Macros in Elixir and Clojure
To me Elixir and Clojure seem like sister programming languages. Both are modern, dynamic and functional languages that embrace concurrency and immutability. Both Elixir and Clojure provide rich metaprogramming capabilities through macros. To understand macros let us look at how typically code execution works in any language ---
Code → Lexical Analysis & Parsing → AST → Execution
When we write normal functions, we usually do not care about how they are evaluated / executed internally. However, with macros we can work with the AST (Abstract Syntax Tree) level directly and manipulate the code there which provides us with unprecedented flexibility to create new language constructs and DSLs.
To see the striking similarities between Clojure and Elixir macros we will take a simple example. We will write a macro in both languages that takes a piece of code and reports the time of execution around it.
To see measurable results we will take an algorithm that is time consuming, nothing better than calculating Collatz sequences. The problem statement -
The following iterative sequence is defined for the set of positive integers:
n → n/2 (n is even)
n → 3n + 1 (n is odd)
Using the rule above and starting with 13, we generate the following sequence: 13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1
It can be seen that this sequence (starting at 13 and finishing at 1) contains 10 terms. Although it has not been proved yet (Collatz Problem), it is thought that all starting numbers finish at 1. Which starting number, under one million, produces the longest chain?
The code in both Elixir and Clojure is simple enough -
And in Clojure -
Now lets build the timing macro in Elixir -
As mentioned earlier, macros work at the AST level, so a macro gets the AST version of the code and then needs to return the manipulated AST version of the code. In Elixir we can produce the AST easily by quote, in example above we inject the code which we need to execute / time and merge it in our custom quoted AST using unquote. So to time our collatz function we can write it as (among other ways) -
or from the REPL -
which gives us -
Clojure essentially uses the same concepts but with a slightly different syntax -
This is essentially the same idea as Elixir, ` is the same as quote and ~ is the same as unquote. Plus Clojure provides # to bind values safely within the macro.
Just for fun, the output of the Timer from Clojure is -
That’s it. We looked at macros and built a simple one in Elixir and Clojure and studied their similarities. As usual, with great power comes great responsibility, macros are powerful but should only be used when a normal function cannot do the job.