Whenever a compiler encounters a piece of code, it’s converted into an abstract syntax tree (AST).
But how can we make changes to the AST that is formed? The answer is through metaprogramming!
In Elixir metaprogramming, a macro is a special function that returns different ASTs based on the arguments (including ASTs) passed to it.
One good example of a macro is redefining the already built unless
macro using the if
construct in Elixir.
defmacro unless(expression, do: block) doquote doif !unquote(expression), do: unquote(block)endend
We use the defmacro
keyword to create an unless
macro that takes an expression
and a block
. Based on the expression
, it executes the block
.
The created macro performs the exact same behavior as the already given unless
macro in Elixir, but in different way.
defmodule Example dodefmacro unless(expression, do: block) doquote doif !unquote(expression), do: unquote(block)endendenddefmodule Test dorequire Exampledef test_unless dox = 10Example.unless(x == 5, do: IO.puts("x is not 5"))endendTest.test_unless()
Whenever the code is parsed and converted into an AST, the compiler does another run-through to expand any encountered macros. It recursively expands the code until it no longer contains any macro calls.
This expansion recursively executes until all macros have been fully expanded into their final generated code.