Monday, January 30, 2017

Elixir - Macros

Macros are one of the most advanced and powerful features of Elixir. As with all advanced features of any language, macros shoild be used sparingly. They make it possible to perform powerful code transformations in compile time. Macros are a fairly complex subject, and it would take a small tutorial to explain it in depth. Here we'll just scratch the surface and get familiar with what macros are and how to use them.

Quote

Before we start talking about macros, let us first look at Elixir internals. An Elixir program can be represented by its own data structures. The building block of an Elixir program is a tuple with three elements. For example, the function call sum(1, 2, 3) is represented internally as:
{:sum, [], [1, 2, 3]}
The first element is the function name, the second is a keyword list containing metadata and the third is the arguments list. You can get this as the output in iex shell if you write:
quote do: sum(1, 2, 3)
Operators are also represented as such tuples. Variables are also represented using such triplets, except the last element is an atom, instead of a list. When quoting more complex expressions, we can see that the code is represented in such tuples, which are often nested inside each other in a structure resembling a tree. Many languages would call such representations an Abstract Syntax Tree (AST). Elixir calls them quoted expressions.

Unquote

Now that we can retrieve the internal structure of our code, how do we modify it? To inject new code or values we use unquote. When we unquote an expression it will be evaluated and injected into the AST. Let us look at an example(in iex shell):
num = 25

quote do: sum(15, num)

quote do: sum(15, unquote(num))
When running above program, it produces following result:
{:sum, [], [15, {:num, [], Elixir}]}
{:sum, [], [15, 25]} 
Here you can see that in the first one, the quote expression does not automatically replace num with 25. We need to unquote this variable if we want to modify the AST.

Macros

So now that we are familiar with quote and unquote, we can unravel the power of metaprogramming in Elixir using macros.
In the simplest of terms macros are special functions designed to return a quoted expression that will be inserted into our application code. Imagine the macro being replaced with the quoted expression rather than called like a function. With macros we have everything necessary to extend Elixir and dynamically add code to our applications.
Let us implement unless as a macro. We will begin by defining the macro using the defmacro macro. Remember that our macro needs to return a quoted expression.
defmodule OurMacro do
  defmacro unless(expr, do: block) do
    quote do
      if !unquote(expr), do: unquote(block)
    end
  end
end

require OurMacro

OurMacro.unless true, do: IO.puts "True Expression"

OurMacro.unless false, do: IO.puts "False expression"
When running above program, it produces following result:
False expression 
What is happening here is our code is being replaced by the quoted code returned by the unless macro. We have unquoted the expression to evaluate it in current context and also unquoted the do block to execute it in its context. This little example shows us the power of metaprogramming using macros in elixir.
Macros can be used in much more complex tasks but should be used sparingly. This is because metaprogramming in general is considered bad practice and should be used only when necessary.

No comments:

Post a Comment