Comment by frwrfwrfeefwf
2 days ago
Typical ignorant take. Lisp macros allow you to create restricted DSLs to prevent bugs from being expressible, including full static type and structured programming systems. Basically no one understands this until they reach the 'lisp enlightenment', so the decades of beating this drum falls on deaf ears. AI is here now so it's time to let it all go.
Of course you feel this way. You just finished reading SICP or maybe you binged PG essays last weekend. But eventually you'll read Simon Peyton Jones and start screaming about functional programming and algebraic type systems. Then, if you're lucky, you'll get a real job and realize that languages are just a tiny part of software engineering.
I'm excited for you to experience that journey.
Wadler's old article comparing Miranda (a Haskell forebear) to Scheme might be a better place to start.
https://www.cs.kent.ac.uk/people/staff/dat/miranda/wadler87....
This was a very pleasant read.
cute but you don't get lisp yet.
I would be very interest in seeing how "getting lisp" enables you to write software that is more successful than the C and C++ software that runs the world. Perhaps you have written software in Lisp demonstrating this? Something you can show us?
2 replies →
Do you like Apple ]['s?
You've got a good point. Code generally needs to be read more than it is written. At the very least, you realize this as you try to revisit something you had done previously and are left scratching your head. Encapsulating the complexity in a custom DSL can be great for simplifying all the code you write in the DSL. But it also raises the stakes - if you ever have to revisit the DSL implementation the complexity is there lurking.
I see what you did there.
"Of course that's your contention. You just got finished readin' some Marxian historian -- Pete Garrison probably. You're gonna be convinced of that 'til next month when you get to James Lemon, and then you're gonna be talkin' about how the economies of Virginia and Pennsylvania were entrepreneurial and capitalist way back in 1740. That's gonna last until next year -- you're gonna be in here regurgitating Gordon Wood, talkin' about, you know, the Pre-revolutionary utopia and the capital-forming effects of military mobilization." Good Will Hunting (1997)
In an awkward, error-prone, non-mathematically rigorous and easily subverted manner. That whole "make invalid states unrepresentable" thing relies upon the fact the semantic structures you're doing it with can't be shadowed silently by other parts of the code, and the safety constraint itself is guaranteed to be completely pure and deterministic. Lisp macros and reader macros fail at that criteria. They aren't any different from any other collection of procedures and their collective interface. Actually, they're a fair bit worse since the natural cognitive overhead from juggling multiple layers of evaluative indirection lends itself to blindspots of edge cases and unsoundness.
I think "macros are a safety mechanism" might qualify for the top 3 "new lisper mania" things I've ever read.
It can be made as mathematically rigorous as anything computable, and as non-awkward as is possible to represent. You either reify the invariants and semantics as statically analysed syntax or they are implicit in your collections of procedures and objects which is far more cognitive load. If you need to judge multiple levels of abstraction while using the DSL then the DSL is the wrong fit for the problem. There is no 'evaluative indirection' because a macro is a _compiler_ and the underlying code can be extremely alien to the DSL semantics.
Any argument against macros must be levelled against compilers in general, as _they are the same thing_. Rust is compiler as a safety mechanism, not different from macros as a safety mechanism.
Again, fallacy by Turing completeness.
It's actually harder to make something not Turing complete - in and of itself this property is absolutely useless and tells you nothing about how practical something is.
3 replies →
The inflection of the copula "can be" is exactly the problem, as is the 'anything computable'. There's a reason Isabelle is used as a proof assistant, and Prolog isn't. The former guarantees sound, mathematical rigor that can't be trivially violated. The latter doesn't. Prolog is much more powerful and flexible than Isabelle. Power and flexibility are disastrous for safety. As someone else said elsewhere in this thread, safety is about restrictions.
In this case, it actually runs deeper than this, there are structural issues which prevent them from being sound right from the beginning. It's the mere fact that the whole behavior of your DSL can be silently changed without touching the module it's defined in, or the file it's being imported into. A well-meaning junior, in a completely different department, can completely destroy your invariants in a completely unrelated part of the codebase. Whether by shadowing runtime functions, or a package-level collision that exists upstream. Even Scheme's hygienic macros don't actually solve this, they just make it less likely. At the point in which you have no behavioral guarantees of semantics, you do not have a safety mechanism at all.
> There is no 'evaluative indirection' because a macro is a _compiler_ and the underlying code can be extremely alien to the DSL semantics.
This is a trivial contradiction. That is precisely evaluative indirection. Yes, stacking multiple compilers on top of each other, especially when their semantics are non-isomorphic, is a massive burden of mental overhead. Given you already have this problem with the mapping between Lisp and binaries, adding even more complicated layers on top of it is a recipe for disaster. Particularly because: you are going to make mistakes in your logic. Macros provide no means to stop you from blowing your own leg off when writing them. That is not what they are designed for.
Finally, you cannot escape the awkwardness of the underlying metaprogramming system, you will always have to wrestle with quotation and quasiquotation. You have to define a DSL before you can use it.
Only at the point in which you treat and use Lisp like an unserious toy, rather than the powerful tool that it is, can the fantasy of "macros are a safety mechanism" begin to make sense.
This is the "fallacy by Turing completeness".
Sure, and you may as well write full systems in Minecraft and create your own type system inside and whatnot.
Macros can make some small specific uses safe, indeed. But they are not comparable to languages with type systems where everything is type safe, unless you literally have written a new type system and compiler to begin with. Which is trivially true in every other language, C can be 100% safe with a much better type system, I just read a text block and compile it as rust code! Wow!
I have no horses in this race and I'm sorry if I misunderstand but I don't think he means to say Lisp is by nature superior to say Haskell. I take it as a statement on the generality inherent in its design. It is by its very nature very low on restrictions. Which is to say your freedom of expression is very nearly as complete as it'll ever be.
Sure you can beat Minecraft into Lisp and then Lisp into Haskell - as Turing made sure of - but you'll be battling a lot of dragons along the way. It's beautiful that you can beat Haskell into letting go of its type system by, say, creating a C compiler in it. It actually still amazes me the universe has this property.
In general I see "building" (I rather call it "conjuring") a system as going from the most general (all of your programming "language") to the specific (your solution). I can see how the most general of languages makes for a comfortable starting point of that downward journey.
I can also see how not starting from The Universe In All Its Infinity Glory (Lisp) but from The Planet Earth (say, Haskell) is helpful if you want to conjure something made for humans, but if you want to go beyond the mortal plane.. (I'm not saying that's a good idea by the way).
My issue is that a language is just as much about what it disallows, as much as about what it allows.
It's absolutely trivial to allow everything, you just have to make a Turing-complete extension either by design or accidentally.
It's much much harder to carefully choose more limiting primitives that only allow you to build stuff that keep certain important properties about them. You can't subtract stuff after the fact.
E.g. something like rust's borrow checker could not be realistically implemented as "a lisp" macro (what does lisp even mean in this case). For that to work you would literally build a rust compiler over s-expressions and you might as well leave off the s-expressions at that point.
And every part that was not compiled by that macro or whatever would be unsafe and could not interact with the "rust-lisp" compiled parts, as you can no longer assert anything about them (there is nothing mandating the usual single writer, xor multiple read-only references laws)
3 replies →
> unless you literally have written a new type system and compiler
This is entirely possible with plain lisp macros. See https://coalton-lang.github.io
Having that natively available as plain Common Lisp code is a very different thing from your "just read a text block and compile it as rust code!" concept. Or at least, the tool chain gymnastics required to make a C program emit its own source in another language... Why?
Well, I was talking about the fundamental part. Of course you can have more practical implementations, like Scala or rust's macro system, that gives you a proper typed AST.
Type safety is a domain specific concern, 99% of the time your domain will not have 'types', often it won't have notions of things at all. But any kind of type safety can be added at any point with macros. Something like rust would be a huge project but it doesn't get easier than macros, see Coalton, and they compose with one another. When you run into the same requirements in another language you are simply stuck source pre-processing. There is no stuck in lisp.
There are plenty of languages with macros though, nothing special about lisps.
And no, language semantics absolutely don't compose. Like you can't just do some kind of optimization in one place if you do some mutation on it in another place. Optimizations work on global assumptions that every part of the codebase have to abide by. Any part not doing it will make the whole thing crumble - and "there is no stuck in lisp" is exactly why you can't have your cake and eat it too.