Some time ago when I was studying Elixir for the first time, I step out with a chapter in the documentation related with something called Macros. When I looked into this, some complicated terms started to show up and then I got scared. Things like:
In order to understand Macros we should have an idea of what these new technical terms means, and how they are related with Elixir.
Almost all blogs, books or videos will say something like Code that writes code, but I really liked the explanation that @iamvery gave in one of his talks.
Metaprogramming it’s just programming …
Programming is performing some work in some data …
Data are quantities on which operations are performed.
So metaprogramming is like programming regularly. However the data where we perform operations can be whatever we want and not just the data structures that the language offers us. For example:
2 + 3
We know that ‘2_’_ and ‘3_’_ are data and ‘+’ is the operation. However in metaprogramming ‘+’ is considered data too. We can manipulate that, have access to it and do whatever we want with it.
When we make programs, the compiler in charge of transforming our code into bytecode needs to know the syntactic structure of our program, so the instructions can be performed as we defined. An abstract syntax three is that representation. Let see some really simple examples:
2 + 3
AST (Abstract Syntax Tree)
2 + 3 == 5
AST (Abstract Syntax Tree)
Elixir has it’s own way to represent AST, this is by converting almost everything to a three element tuple, just to recap a little bit on this, the Elixir compiler has some steps before it produces the .beam file, this is a really brief schema:
Elixir compiler phases
When using macros we are scripting and producing Elixir AST, we won’t touch the Expand Macros and Erlang AST. But, it’s good to know that those steps are there.
Basic data structures like atom, integer, float, string, list and two element tuples are considered literals. It means that when the compiler process that, they are the the same in AST form. Example:
AST Literal
In the other hand, everything else is transformed to a three element tuple which has this basic structure:
If it is a variable:
First element is the variable name, second item is the metadata that it’s needed in order to this variable to exist (normally empty), and the last item is the context.
Variable to Elixir AST
If it is a call
First element is the function name, second is just metadata (normally you won’t care about this), and last item are the arguments.
Function call to Elixir AST
Probably at this point you are thinking on quitting this article, AST are complicated nested structures. However, there are some helpful functions (actually they are Macros too) that can help us manage all this structures.
May be if you see an example of quote, you can understand what it is and how to use it:
iex> quote do: 2 + 3{:+, [context: Elixir, import: Kernel], [2, 3]}
we can use it inline or multiline as any other function or macro.
iex(1)> quote do...(2)> if 2 + 3 == 5 do...(3)> "this is true"...(4)> else...(5)> "this is false"...(6)> end...(7)> end
{:if, [context: Elixir, import: Kernel],[{:==, [context: Elixir, import: Kernel],[{:+, [context: Elixir, import: Kernel], [2, 3]}, 5]},[do: "this is true", else: "this is false"]]}
With unquote, the first thing that went to my mind was:
iex(1)> unquote do: {:+, [context: Elixir, import: Kernel], [2, 3]}"2 + 3"
but that is not how unquote works, let’s put it this way.
quote and unquote are like “interpolated strings”
Imagine the next code:
iex(1)> name = "George""George"iex(2)> "Hello name""Hello name"
We need to evaluate the variable name, with strings we use #{}
iex(1)> name = "George""Geroge"iex(2)> "Hello #{name}""Hello George"
With quoted expressions we use unquote.
iex(1)> number = 3iex(2)> quote do: 2 + unquote(number){:+, [context: Elixir, import: Kernel], [2, 3]}
Finally, the main idea of this article is to show you what Macros are but as I said in the beginning, there are some other concepts that we needed to understand in order to write Macros.
Let’s define our first Macro:
defmodule MyMacros do
defmacro nice_print({:+, _meta, [lhs, rhs]}) doquote doIO.puts """#{unquote(lhs)}+ #{unquote(rhs)}--#{unquote(lhs+rhs)}"""endend
end
See how immediately we use quote, that’s because macros always return an Elixir AST, if you don’t you will get an error.
Let’s use that Macro:
iex(1)> require MyMacros[MyMacros]iex(2)> MyMacros.nice_print 2 + 32+ 3
5
Notice that our macro receives a three element tuple, that’s the Elixir AST of 2 + 3 in this case and not the evaluation of that expression which is 5.quote & unquote are not the only tools that we have in order to manage Macros.
Macro.to_string
iex(1)> expression = quote do: 2 + 3{:+, [context: Elixir, import: Kernel], [2, 3]}iex(2)> Macro.to_string(expression)“2 + 3”
Code.eval_quoted
iex(1)> expression = quote do: 2 + 3{:+, [context: Elixir, import: Kernel], [2, 3]}iex(2)> Code.eval_quoted(expression){5, []}
In Chris McCord book “Metaprogramming Elixir” he has a section called Macro Rules, and his first rule is:
Don’t Write Macros
But why?, they are powerful and amazing, aren’t it?… I guess that’s the first reason, they are so powerful that you can get addicted to overuse them and by overuse them you code can look ugly and hard to read.
In one of the Jesse Anderson talks he show one really interesting quote:
Code is read many more times than it is written, which means that the ultimate cost of code is in its reading.
And for me that’s true, we don’t have to lose the idea to writing code beautiful and easy to read.
The benefits of using Macros are the metaprogramming part, we can do things that with normal functions we can’t, arguments in functions are evaluated before we can even use them in our function body, with Macros this is not the case since we only receive the Elixir AST version of our expressions.
Macros are also good for hiding boilerplate, imagine that you had to write this
case(true) dox when x in [false, nil] ->false_ ->trueend
instead of this
if true dotrueelsefalseend
So, with macros we have this possibility of hiding code by generating it inside the macro.
Hopefully you get the idea of what Macros are and how to use them, for me this topic is really awesome since this is the first time I do metaprogramming. Elixir make it easy and maintainable to read. If you have any question here is my twitter @jorgechavz. Thanks!
Macros don’t stop here, there is so many information out there related with this topic, I will leave some links here that really helped me to understand this interesting topic.
**The minimum knowledge you need to start Metaprogramming in Elixir**https://dockyard.com/blog/2016/08/16/the-minumum-knowledge-you-need-to-start-metaprogramming-in-elixir
Don’t Write Macros But Do Learn How They Work — Jesse Anderson https://www.youtube.com/watch?v=Bo48sQDb-hk
Write less, do more (and have fun!) with elixir macrosVideo: https://www.youtube.com/watch?v=mkoYqXdXl5YSlides: http://slides.com/chrismccord/elixir-macros#/
Understanding Elixir Macroshttp://theerlangelist.com/article/macros_1