Macro is a code that writes code. Macros are one of the most advanced and powerful features of Elixir. They make it possible to perform powerful code transformations in compilation time. Before jumping into macros lets have a basic understanding of the abstract syntax tree (AST)
What is Abstract Syntax Tree(AST)?
Abstract syntax trees are used to represent the structure of a program’s source code for the compiler to use. Compiler or interpreter generates AST before generating the machine code.
Let's take a look at AST representation for expression 2 * 6 - 9 looks like this
Elixir's creator designed the language differently. In elixir, AST is exposed in a form that can be represented by Elixir’s own data structures. you can represent elixir code in AST format by using a quote macro. you can pass a chunk of code as an argument to quote macro it will return the AST of that syntax
quote:
The main purpose of the quote is to retrieve an internal representation of the code
iex(1)> quote do: 1 + 2
{:+, [context: Elixir, import: Kernel], [1, 2]}
Elixir AST is a three-element tuple. the first element represents the function, the second element represents metadata and the third element represents the arguments list
you can use Macro.to_string to convert AST to string
iex(6)> Macro.to_string( quote do: 1 + 2)
"1 + 2"
you can evaluate AST using Code.eval_quoted function
iex(12)> ast = quote do: 1 + 2
{:+, [context: Elixir, import: Kernel], [1, 2]}
iex(13)> Code.eval_quoted(ast)
{3, []}
unquote:
unquote is another macro you need to be aware of, It helps to inject
the chunk of code inside the AST, you can not access outside values
inside the macro directly, unquote helps you to access those values
eg: iex(7)> num = 1
iex(8)> quote do: num + 2
{:+, [context: Elixir, import: Kernel], [{:num, [], Elixir}, 34]}
unquote works smiliarly as string inpterpoation, it injects values inside quote
iex(31)> quote do: unquote(num) + 2
{:+, [context: Elixir, import: Kernel], [1, 2]}
I hope you got an idea of What AST is and how it represents in elixir. If you are still confused don't worry you will get clarity by the end of this blog.
You can define macro using defmacro keyword. macros accept AST as argument and return AST value. Let's build a macro together to get more clarity
If you are from the ruby background you are already familiar with unless statements. basically, unless statement works opposite of the if statement
if 2== 2 , do: IO.puts "Hello", else: "World"
In this statement, The output will be Hello
` if we use unless in the above expression `unless 2== 2, do: IO.
puts "this", else: "that"
the result will be that
Before building our own unless macro let's define rules first. Rules are very simple It should accept two arguments, the first argument is an expression that needs to be evaluated and the second argument is a list of do, else keyword arguments, if the expression is false or nil do block will be executed otherwise else block will be executed
When you pass any argument it macro it will implicitly convert into AST statement
defmodule ControlFlow do
defmacro unless!(expression, [do: this, else: that] ) do
quote do
case (unquote(expression)) do
x when x in [false, nil] -> unquote(this)
_ -> unquote(that)
end
end
end
end
Macros always return AST value, we wrapped our entire logic inside the quote block. we evaluated the expression using unquote macro it will give true, false, or nil value. If the expression is false or nil, we unquote `this` statement otherwise we unquote `that` statement
Lets test this in iex terminal
ex(13)> require ControlFlow
ControlFlow
iex(14)> import ControlFlow
ControlFlow
iex(15)> unless! 2 == 3 , do: IO.puts("Execute unless macro"), else: IO.puts("Failed to excute unless macro ")
Execute unless macro
:ok
we succefully created our own macro.
We can perform pattern matching on macros. Let's define a new macro add, If the input to the macro pattern match with tuple {:+, _, [lhs, rhs] } and perform addition operation on lhs and rhs
defmodule ControlFlow do
defmacro add({:+, _, [lhs, rhs]}) do
quote do
unquote(lhs + rhs)
end
end
end
Macros are really powerful most of the elixir code is written using Macros
I hope you understand the power of macros in elixir. You can learn more about elixirs and the Metaprogramming in Elixir by ChrisMcCord