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.
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!
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.
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 <! and >! 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."
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.
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).
Could you explain why Paul Graham's book "On Lisp" is wrong? Or misguided? Or misunderstood by newbies like me? 30 years of c++ with a lot of dealing with templates and I'm not feeling the mandatory function purity here.
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.
And apparently neither Penrose nor Escher knew about Oscar Reutersvärd's work! Oscar got robbed lol.
>It wasn’t until 1984 that Roger Penrose (whose father Lionel died in 1972) discovered that Oscar Reutersvärd had invented the triangle and the stairs much earlier.
https://escherinhetpaleis.nl/en/about-escher/escher-today/os...
This claim didn't age well.
At the same time, back then the thought was that symbolic AI was the answer, and for sure Hofstadter's understanding of intelligence, and the emergence of consciousness from matter, had everything to do with symbols. He's talked about this, very interesting, although this is not a 'feel good' artifact from the Hofstadter canon lol. https://www.youtube.com/watch?v=R6e08RnJyxo&t=227s
Not really your question--others have provided examples of type systems as macros--but they can't, they can only do local rewrites. You couldn't, for example, express mutation or non-local control flow if you didn't have them already; On the expressive power of programming languages [0] is rather theoretical but does set out the boundary of what macros can and can't do. That they're local means everyone has to buy into your type system if you want it to be a macro, though.
[0] https://www.sciencedirect.com/science/article/pii/0167642391... though https://www.youtube.com/watch?v=43XaZEn2aLc might be a useful walkthrough of the ideas being presented
lexilambda created Typed Racket on top of Racket. https://github.com/racket/typed-racket
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