Lisp has macros.
Macros are functions you write that operate on that syntax tree during compilation.
It’s not obvious at all what they’re for.
I’ll discuss two ways that macros are used.
One use of macros is what lispers call “bottom-up development”. Rubyists do it too, but they call it “domain specific language”.
attr_accessor, and active record’s
belongs_to are great examples in ruby. They’re implemented using
its ‘metaprogramming capabilities’, where you can interact with classes. Lisp code doesn’t usually use classes, so you operate directly on the syntax tree, which is easy because your code is a literally a syntax tree literal: nested
lists of functions and argumenmts.
Ruby’s metaprogramming is quite complex, but lisp’s macros are less so because they use the same functions that you use on the other lists and arrays that make up your program.
Another use of macros relies on the fact that they’re executed during compile time, rather than runtime.
When I’m writing lisp, I put my documentation and testing inline. In clojurescript, for example, whenever I define a react component, on the next line I pass it as an argument to a macro, along with maybe some sets of test props. In production, the macro returns no code. In development, it returns code that generates a new set of routes in the app for testing and documentation, including the component’s docstring, if I’ve defined one (clojure, like python, uses in-language docstrings rather than comments above functions), and an instance with each set of test props. It would be crazy to build the same thing with babel plugins.
Distinction between compile-time and run-time code within the same app is useful in more subtle ways, too.