Index ¦ Archives ¦ Atom

Metaprogramming Elixir Notes - 1

For the past 6 months, I have been exploring Elixir language and it's ecosystem. Elixir is a dynamic functional language. It compiles to bytecode to run on BEAM (Erlang's Virtual Machine) similar to how Java code is compiled to bytecode to run on JVM. There are fundamental differences between BEAM and JVM.

Elixir is relatively a new language compared with Java and Python. Development of this language started in 2012 and as of writing it we have Elixir v1.10. Almost all functional languages are declarative in style. This is the first time, I am learning to write programs in declarative style rather than imperative style even though Python also has some declarative and functional approach here and there but it really doesn't enforce a particular style.

That said, the word Metaprogramming has been fascinating to me since I came across the phrase code that writes code. Learning to program with Elixir and learning all other new terminologies of Erlang ecosystem so far has been very pleasant for me. Thanks to Sasa Juric's book. That said, I am also learning the only web framework as of today available for Elixir, Phoenix. The author of Phoenix had leveraged Elixir's metaprogramming abilities using macros and has also published a book titled Metaprogramming Elixir. I have started to read this book too and this blog post is the notes that I took for Chapter 1 of the book.

Notes 1

  1. Metaprogramming allows us to do more by writing less code.
  2. Macros facilitate metaprogramming in Elixir.
  3. Abstract Syntax Tree (AST) is how compilers represent the code we write before processing them and converting to bytecode.
  4. Macros are language feature that allows us to write code that writes code.
  5. quote is a macro in Elixir that will give us the AST representation of our Elixir code using Elixir's native data structure called tuple {}
  6. Any expression in Elixir, represented in AST will be a tuple with three units. {:atom, [context], [arguments]}
  7. :atom will be mostly function names, [context] is a list of contexts required to perform the operation, last one is list of arguments passed to the function.
  8. Macro's in Elixir taskes AST representation as input and returns either the modified or unmodified version of the AST thus allows us to inject code into code.
  9. Metaprogramming turns us from being a language consumer to language creator, because Macro's allows us to extend the language.
  10. Metaprogramming in Elixir asks us to throw away the notion of rigid keywords & opaque langugage internals.
  11. unquote is a macro used within quote block that allows us to perform the inverse operation of quote that is to inject the expressions into the AST. They cannot be used outside quote macro.
  12. Chris McCord, the author of Phoenix framework and this book gives us 2 cautions or rules while trying to learn Metaprogramming.
  13. First, Don't Write Macros. When taken too far in depth, macro's can make programs difficult to debug and reason about. Think about the movie Inception. Which dream are they in?
  14. Second, don't let the first rule demotivate you to learn it. Approach and Explore Macros and Metaprogramming with open and curious mind.
  15. One can think quote is like enclosing a variable with double quotes and unquote as, removing it thus depicting the value.
  16. Ex: age = 2, quote means, "age", while unquote means age
  17. Elixir also provides us a module called Macro to playaround with macros within the language. Macro.expand and Macro.expand_once can be used to explore how compiler expands the Macro's at compile time.
  18. Code can be injected in two contexts. One is Macro's definition context (outside quote) and the other is the macro's caller invocation context (within quote).
  19. Elixir also provides a concept of Hygine. i.e keeping pollution-free contexts. By default, elixir prevents access to caller scope variable inside the macros, but if needed, they can be polluted explicitly by using var!(variable_name).
  20. It is to be noted that most of the language constructs of Elixir itself is written as macros with a small set of core native types.
  21. Macros also allows us to create Domain Specific language terminologies.

Example Macro & Usage:

defmodule MyMacro do
  defmacro debug({:+, _context, [lhs, rhs]}) do
    IO.puts "Macro definition Context"
    quote do
        IO.puts "Caller's context"
        lhs = unquote(lhs)
        rhs = unquote(rhs)
        result = lhs + rhs
        IO.puts "#{lhs} plus #{rhs} equals to #{result}"
        result
    end
  end
end

iex>require MyMacro
iex>MyMacro.debug(2 + 3)

Notes for Chapter 1 ends here. Will share notes for Chapter 2 when I finish it.

© Prasanna Venkadesh. Creative Commons Attribution ShareAlike. Theme by Giulio Fidente on github.