Live data from Hacker News

Gödel, Escher, Elisp: The Beauty of Macros

chiply.dev

21–30 of 44 posts

Re: Gödel, Escher, Elisp: The Beauty of Macros

#23
post #3

Remember the golden rule of Lisp macros: don't write a macro.

That is badly generalized proverb. Clojure has unhygienic macros, no expander extension points, and no error-message layer, so "don't" is a rational default there. Yet, for example Racket writes macros that write macros as a matter of course. And that's because the language has the infrastructure: hygiene by default, phase separation, `syntax-parse` with grammar-quality error messages, a module system that actually knows about compile-time dependencies.

But even with some limitations, there are cases in Clojure where macros do achieve things otherwise unattainable. One practical example is multi-host code generation. You can have a Clojurescript macro that parses and analyzes some javascript lib, doing that on JVM side, while emmiting code generated to run in Javascript. Hyperfiddle/Electric is the best example of that kind of macro ingenuity.

Or take core.async's `go`. It takes an arbitrary body, analyzes it into an AST, and rewrites it into a state machine so that ! can park and resume. On the JVM it lets you avoid blocking threads. In Clojurescript it is the only way to have the model at all, because JS has no threads to block.

Nothing about that is expressible as a function. The transformation needs the entire body as data. Same family: core.match compiles a pattern matrix into a decision tree. And there are many more example use cases for macros.

"Don't write macros" rule has the second part: "unless you truly have no choice."

Re: Gödel, Escher, Elisp: The Beauty of Macros

#24
post #11
post #4

> To anticipate a common question: why couldn't my-unless be a function? Function arguments are evaluated eagerly, before the function ever sees them. Interesting, so if you’re using a lazy language then you don’t need a macro here and could write my-unless as a function.

Yes, the D language has that as a feature in function arguments! https://dlang.org/articles/lazy-evaluation.html It makes it hard to know when things run. In Lisp you also have that problem everywhere, of course. As the post shows this allows you to do stuff that looks like extending the syntax of the language. I can’t decide if I love it or hate it!

Props to D for having such a simple implementation of fexprs[1].

The downside compared to macros I think is that, as argument expressions become lambdas, it becomes harder to manipulate them. Check the cond example which needs two fexprs, whereas one macro could do it.

Not sure about D, but if it were lisp, even lambdas could be manipulated as lists. Macro args not having the lambda wrapping just seems simpler.

[1] https://en.wikipedia.org/wiki/Fexpr

Re: Gödel, Escher, Elisp: The Beauty of Macros

#25
The Computational Beauty of Nature it's similar and it has the code of the book there:

https://github.com/gwf/CBofN

Some tools might crash X under CWM for OpenBSD, switch to FVWM or use some tool like mimalloc.

On Lisp, the book basically builds integers based on conses, kinda like Peano Axioms.

Re: Gödel, Escher, Elisp: The Beauty of Macros

#26
post #9

If macros can implement arbitrary language features then how come elisp has never built a type system?

I would expect you can. lexilambda created Typed Racket on top of Racket. https://github.com/racket/typed-racket

Another approach, using macro expansion to perform the type checking: https://docs.racket-lang.org/turnstile/

Turnstile relies on the fact that in Racket, you can have identifier macros [1] which can be expanded in pretty much any location (except binding, naturally). Racket is unique in this regard. Clojure has a mechanism to get roughly the same thing, but it's complicated. [2]

1: https://docs.racket-lang.org/guide/pattern-macros.html#%28pa...

2: https://lambdaland.org/files/2024_ecoop_type_tailoring.pdf

Re: Gödel, Escher, Elisp: The Beauty of Macros

#27
post #3

Remember the golden rule of Lisp macros: don't write a macro.

That is badly generalized proverb. Clojure has unhygienic macros, no expander extension points, and no error-message layer, so "don't" is a rational default there. Yet, for example Racket writes macros that write macros as a matter of course. And that's because the language has the infrastructure: hygiene by default, phase separation, `syntax-parse` with grammar-quality error messages, a module system that actually k…

My own personal rule is that every Lisp programmer should understand macros and very rarely reach for them. They are a super-power, but easy to misuse. I think Paul Graham is partly responsible for the “Lisp = macros” thinking and the general overuse of them. On Lisp was a great book but it does lots of things I wouldn’t consider good practice today. After Graham popularized Lisp and macros, there was a period when every Lisp newbie, myself included, was writing macros all the time. Now that I’m older, I know better. Clojure is my daily driver and I haven’t written a macro in years (though I’ve debugged ones other people have written). But I know defmacro is there and I can use it anytime I really need to.

Re: Gödel, Escher, Elisp: The Beauty of Macros

#28
post #13
post #3

Remember the golden rule of Lisp macros: don't write a macro.

Meh, that seems to be a bit of a clojure thing. In the Racket world, with hygienic macros and phase separation, they'll routinely write macros returning macros, etc.

Macros returning macros happens in every Lisp, including Clojure. It’s no biggie. Hygiene has nothing to do with it and there are ways of properly managing unique symbols in all Lisps, even if some dialects are more manual than in Scheme/Racket.
Post reply on HN