Or you could wrap arguments in lambdas (anonymous functions) for eagerly-evaluted languages. Lisp unfortunately has a bulky syntax for lambdas, compared to something like Smalltalk. Of course, you can fix this with reader macros in Common Lisp (but no one does). Clojure has a shorter syntax I believe as well: #().
Author here. I think in general this is sound advice, but in Emacs it is an incredibly convenient utility. I use them all the time. Mostly simple things like wrapping function definitions, or even other macros that wrap function definitions. Most of my lisp experience is in Emacs where the config layer is essentially exposed through an elisp API. In that context, I find myself using macros a lot. But if I was building some software from scratch, like say a data processing system, I can't immediately think of scenarios where I would define macros.
Worth mentioning in Elisp that even if you don't define your own macros, you use them more often than you would think. defun, unless, def-custom, etc (more examples in the blog post) are all macros. The fact they look and feel like non-macros like special forms and built in functions implemented in C is part of what makes elisp so cool to me. There are plenty of homoiconic languages, but you rarely feel the distinction between program and data in elisp, and macro-supporting lisps in general.
If you look at the definition of a macro like defcustom, you'll see that it just expands to a function call, where all the logic is implemented. This could also be implemented in the macro, but that is more complicated and brittle, due to the risk of double-evaluations and having to produce code that will (usually) later evaluate with the intended side effect.
Furthermore, macros and functions constitute a kind of function coloring. To use a macro in a function, like defcustom, you couldn't pass the name of the user option you are defining in as a symbol, as that is not evaluated. So instead you'd have to call eval on a runtime constructed expression, which is a cludge.
So I agree with the top comment, to avoid macros is a sign of Lisp maturity. It is easy and fun to admire them when you are coming from languages with arbitrary restrictions in their macro systems like C, but ones you get used to them you don't treat them with any more wonder than any other arbitrary restriction that a language may lack (bad ad hoc example: nobody praises C for the lack of a CALL keyword).
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.
Can you elaborate? Roger Penrose and his father Lionel did independently discover and popularize the Penrose Stairs and Penrose Triangle but Oscar Reutersvärd had created both years earlier. What do you think should be said about Penrose?
Yeah, I think the story is that Penrose saw an illusion in an Escher print that encouraged him to create the Penrose Triangle, his dad Lionel created the Penrose staircase, and then sent a copy of these in the paper "Impossible objects..." to Escher, who then used the staircase in "Ascending and Descending" and others. It's kind of cool that they cite eachother in these works as inspiration here. I bet Escher could have made an interesting print of them pointing at one another in some kind of impossible object.
And apparently neither Penrose nor Escher knew about Oscar Reutersvärd's work! Oscar got robbed lol.
Typed racket too, might I add. I just imagine static typing around stable core APIs would help users understand how eglot and vertico compare to prior art. The way I learned how the completion stack works was to ask claude to show me what the elisp entrypoints would look like if implemented in typescript. It's not because I'm unfamiliar with lisp, I'm not, it's just easier to reason with the world in terms of data structures and their contracts.
> 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.
Or you could wrap arguments in lambdas (anonymous functions) for eagerly-evaluted languages. Lisp unfortunately has a bulky syntax for lambdas, compared to something like Smalltalk. Of course, you can fix this with reader macros in Common Lisp (but no one does). Clojure has a shorter syntax I believe as well: #().
You could do that as well with lisp by passing around lambdas to functions but that adds unnecessary syntax.
Indeed:
https://hackage-content.haskell.org/package/base-4.22.0.0/do...
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!
Remember the golden rule of Lisp macros: don't write a macro.
Author here. I think in general this is sound advice, but in Emacs it is an incredibly convenient utility. I use them all the time. Mostly simple things like wrapping function definitions, or even other macros that wrap function definitions. Most of my lisp experience is in Emacs where the config layer is essentially exposed through an elisp API. In that context, I find myself using macros a lot. But if I was building some software from scratch, like say a data processing system, I can't immediately think of scenarios where I would define macros.
Worth mentioning in Elisp that even if you don't define your own macros, you use them more often than you would think. defun, unless, def-custom, etc (more examples in the blog post) are all macros. The fact they look and feel like non-macros like special forms and built in functions implemented in C is part of what makes elisp so cool to me. There are plenty of homoiconic languages, but you rarely feel the distinction between program and data in elisp, and macro-supporting lisps in general.
If you look at the definition of a macro like defcustom, you'll see that it just expands to a function call, where all the logic is implemented. This could also be implemented in the macro, but that is more complicated and brittle, due to the risk of double-evaluations and having to produce code that will (usually) later evaluate with the intended side effect.
Furthermore, macros and functions constitute a kind of function coloring. To use a macro in a function, like defcustom, you couldn't pass the name of the user option you are defining in as a symbol, as that is not evaluated. So instead you'd have to call eval on a runtime constructed expression, which is a cludge.
So I agree with the top comment, to avoid macros is a sign of Lisp maturity. It is easy and fun to admire them when you are coming from languages with arbitrary restrictions in their macro systems like C, but ones you get used to them you don't treat them with any more wonder than any other arbitrary restriction that a language may lack (bad ad hoc example: nobody praises C for the lack of a CALL keyword).
Why?
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.
I always thought it odd that Penrose gets dropped from the Escher conversations.
Can you elaborate? Roger Penrose and his father Lionel did independently discover and popularize the Penrose Stairs and Penrose Triangle but Oscar Reutersvärd had created both years earlier. What do you think should be said about Penrose?
Yeah, I think the story is that Penrose saw an illusion in an Escher print that encouraged him to create the Penrose Triangle, his dad Lionel created the Penrose staircase, and then sent a copy of these in the paper "Impossible objects..." to Escher, who then used the staircase in "Ascending and Descending" and others. It's kind of cool that they cite eachother in these works as inspiration here. I bet Escher could have made an interesting print of them pointing at one another in some kind of impossible object.
And apparently neither Penrose nor Escher knew about Oscar Reutersvärd's work! Oscar got robbed lol.
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
Well, not elisp, but
https://coalton-lang.github.io
or maybe
https://shen-language.github.io
Typed racket too, might I add. I just imagine static typing around stable core APIs would help users understand how eglot and vertico compare to prior art. The way I learned how the completion stack works was to ask claude to show me what the elisp entrypoints would look like if implemented in typescript. It's not because I'm unfamiliar with lisp, I'm not, it's just easier to reason with the world in terms of data structures and their contracts.
CL is the big cousin of Elisp, almost.