I think being able to define additional rules in a language is a reasonable feature and sometimes would make life a whole lot easier, but I think when implemented through text macros rather than rewriting the underlying program graph along with accounting for all the constraints, that causes most issues.
Program graph rewriting is of course more complex, but can be better defined with regards to acceptable inputs and more defensive as opposed to letting the next step of compiler or interpreter handle it.
The other problem is that most IDEs don't offer an expand macro (or similar) option for the rewritten parts (or I haven't seen them doing that), so observing what the final code looks like requires additional steps. In some ways its similar to functions, but most languages have functions that have a well defined input/output structure whereas macros can do some unexpected things depending on the inputs.
Insert mandatory observation here that LISP was there decades ago. I don't know about SLIME in Emacs, but if you use SLIMV in Vim, you have a keyboard shortcut for MACROEXPAND already there.
Rust has been a practical experiment in macros for ten years now. I think it is safe to say the results are in: macros are great, and you just needed to have a better style of macros. Making and using something like `tokio::select!` in C-style macros is miserable, but `tokio::select!` is easy to use and understand in Rust.
The main problem with macros (and similar language features) is that they turn every codebase into its own DSL. When done tastefully it makes the code more readable, but there's still an overhead for newcomers to the codebase - they need to learn your DSL before they can be productive.
LLMs may make this overhead less visible to you, but surely it's still there? I'd rather more of my tokens went towards solving the actual task at hand, vs figuring out a custom syntax (and re-learning it on every fresh context window).
At the risk of being a bit of a douchebag, it seems like nearly every tech opinion piece I read now boils down to "things used to be hard but they aren't anymore because LLMs can understand things faster than we can, so can ignore/change fundamentals!"
I didn't really find the argument convincing the first time and I don't really find it convincing now.
That said, with regards to macros, I actually do agree that the fear against them is broadly overblown. I have seen scary terrible horrifying macro soup in Clojure, but that has generally been outlier cases. Something like core.async abuses the hell out of macros, and despite that I think it very often increases readability and understanding of the language.
And even if LLMs do our thinking for us, it's still not a reason to fork the programming language you're using, unless you expect me to believe an LLM can understand `foo |> bar` but it can't understand `bar(foo(x))`. If you're not looking at the code, it doesn't matter how pretty the code is anymore.
> it seems like nearly every tech opinion piece I read now boils down to "things used to be hard but they aren't anymore because LLMs can understand things faster than we can, so can ignore/change fundamentals!"
Sure, LLM coding agents being capable doesn't mean we can ignore fundamentals.
But LLM coding agents surely adjust the cost/benefit considerations for all kinds of programming efforts.
Arranging your code in such a way that it's so complicated you need an LLM to understand it is surely silly.
Using an LLM to write a macro because you weren't going to learn how to write macros and deal with all the subtle cases? I think that's arguable.
It should mean both. They need to learn how to do it by hand and then learn how to lean on a navigation system. If only GPS fails, then the celestial navigation program fallback runs without a hitch. If shit hits the fan, then they can break out the manual and remember how to do it by hand.
Not sure what this guy is on about. Languages with extensible syntax (through macros, through term rewriting, through intrusive function like decorators) have always been available, though notably less popular. Languages also can support extensible features through reflection: runtime inspection of structure of user-defined types.
- Any language with bicameral syntax (the lisps, etc)
- Rebol and Red
- Smalltalk, Self, etc.
People tend to not like these for most programming problems due to the eventual maintenance burden and the steep up front cost of becoming competent with them. But the title and thesis of this post is a bit of a head scratcher: we've always been able to change languages designed to be changed, and mainstream high level language no. 2, Lisp, was one such language. As always, whether or not a language supports macros or some general metaprogramming facility is a design tradeoff both for the complexity of the compiler and the implicit complexity of the codebase (knowing that the language doesn't prevent you from defining new operators with semantics that may go counter to the intuition employed in the base language).
Program graph rewriting is of course more complex, but can be better defined with regards to acceptable inputs and more defensive as opposed to letting the next step of compiler or interpreter handle it.
The other problem is that most IDEs don't offer an expand macro (or similar) option for the rewritten parts (or I haven't seen them doing that), so observing what the final code looks like requires additional steps. In some ways its similar to functions, but most languages have functions that have a well defined input/output structure whereas macros can do some unexpected things depending on the inputs.
Insert mandatory observation here that LISP was there decades ago. I don't know about SLIME in Emacs, but if you use SLIMV in Vim, you have a keyboard shortcut for MACROEXPAND already there.
LLMs may make this overhead less visible to you, but surely it's still there? I'd rather more of my tokens went towards solving the actual task at hand, vs figuring out a custom syntax (and re-learning it on every fresh context window).
I didn't really find the argument convincing the first time and I don't really find it convincing now.
That said, with regards to macros, I actually do agree that the fear against them is broadly overblown. I have seen scary terrible horrifying macro soup in Clojure, but that has generally been outlier cases. Something like core.async abuses the hell out of macros, and despite that I think it very often increases readability and understanding of the language.
Sure, LLM coding agents being capable doesn't mean we can ignore fundamentals.
But LLM coding agents surely adjust the cost/benefit considerations for all kinds of programming efforts.
Arranging your code in such a way that it's so complicated you need an LLM to understand it is surely silly.
Using an LLM to write a macro because you weren't going to learn how to write macros and deal with all the subtle cases? I think that's arguable.
It's going to be a hard come-to-Beevis moment if the magic signal goes away and people have to remember celestial navigation.
Not sure you can even find a sextant on most vessels.
So too these AI services.
Doing a fix, like playing music well at all, takes copious practice.
- Any language with bicameral syntax (the lisps, etc)
- Rebol and Red
- Smalltalk, Self, etc.
People tend to not like these for most programming problems due to the eventual maintenance burden and the steep up front cost of becoming competent with them. But the title and thesis of this post is a bit of a head scratcher: we've always been able to change languages designed to be changed, and mainstream high level language no. 2, Lisp, was one such language. As always, whether or not a language supports macros or some general metaprogramming facility is a design tradeoff both for the complexity of the compiler and the implicit complexity of the codebase (knowing that the language doesn't prevent you from defining new operators with semantics that may go counter to the intuition employed in the base language).
I want it because LLMs make it easy to do without having to think.