Macros - how are they different to functions? When should they be used? How do they differ to C's macros?

Hello, I’m new to Elixir.
I was reading about the macros of Elixir but didn’t quite get it. I know C and familiar with its marcos.
Although I didn’t quite understand Elixir’s macros yet. Could you answer my basic questions about macros:

  1. What is the difference between functions and macros in Elixir?
  2. Why or when Should I use them?
  3. How they are different for C’s macros?
3 Likes

Re 1. Given:

my_thing(:some_param)

If my_thing is a macro, the line above will be evaluated at compilation time, otherwise it will be evaluated at run-time. Note that macro can invoke a function which means the function will be invoked at compilation time, which in turn means you can do all kinds of fancy and crazy stuff at compile-time.

Re 2. Use macros if functions are not enough to remove boilerplate. The line is blurry. Generally, avoid using macros.

Re 3. Elixir macros are safer (hygienic) and more powerful. They transform a piece of Elixir code into a different piece of Elixir code. The coolest thing is that the piece of code you’re working with is expressed as an Elixir data structure.

I highly recommend going through the blog series about macros by Saša Jurić.

4 Likes

Didn’t quite understand how C and Elixir macros are different? C also transforms a piece of C code into a different piece of C code right?

Does it? Last time I used them it worked more like a templating engine. AFAIK Elixir macros can inspect the code in ways that is not possible in C. For example:

iex(1)> quote do  # when macros receive args, they are quoted; how does it look like?
...(1)>   1 + 2
...(1)> end
{:+, [context: Elixir, import: Kernel], [1, 2]}

iex(2)> defmodule Fun do
...(2)>   defmacro transform({:+, context, args}) do  # must return code in quoted form
...(2)>     {:-, context, args}  # flip the + into -; leave rest as-is
...(2)>   end
...(2)> end
iex(3)> require Fun  # we need to opt-in to use macros
iex(4)> Fun.transform(1 + 2)  # transforms 1 + 2 into 1 - 2
-1

Note that since the example happens in IEx, the expression 1 - 2 is instantly evaluated.

2 Likes

C Macros are very similar to the “search and replace” function in your editor, as that is exactly how they are implemented. They are very dumb.

Elixir macros though are able to “understand” the arguments they got and produce completely different code for different arguments.

3 Likes

C Macro’s are indeed a templating engine, more similar to EEX than anything else, entirely textual and not AST aware at all. Elixir Macro’s are AST transformers, more like LISP macro’s, which is closer to C++ templates. Essentially an Elixir Macro is a function that executes at compile time to transform one AST form (whatever is passed in to it as an AST instead of as values) and returns a new AST to put in it’s place, and that continues resolving down until it is finally at the ‘base’ AST level and no more expansions can happen, which is then when it is compiled into Erlang.

3 Likes

C macros result in direct, textual replacement at the point the preprocessor encounters it. The preprocessor provides some simple flow control (e.g.#ifdef) but overall your ability to generate code from the Macro system is primitive and it is based on simple text substitution.

Elixir’s macro system allows you to use the full power of the language to write Elixir code. You can use conditionals, functions from the standard library, operations, etc… all to build Elixir code at compile time. So if you want to define a function in a module for each string from a list, it can be done.

3 Likes