What is Macro?

More from Author
Krupananda

Senior Software Engineer

Tags:
macro
6 min read

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

Copy 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

Copy Code
                  
  iex(6)> Macro.to_string( quote do: 1 + 2)
      "1 + 2"
                  
                

you can evaluate AST using Code.eval_quoted function

Copy Code
                  
  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

Copy Code
                  
  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

Copy Code
                  
  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

Copy Code
                
  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

Copy Code
              
  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

Copy Code
              
  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

Back To Blogs


Find out our capabilities to match your requirements

contact us