1
votes

I'm learning Elixir now and I'm really confused with quote and unquote. Here is how we create a function with a dynamic name using a macro:

defmacro create_fun(n) do
  quote do: def unquote(:"times_#{n}")(a), do: a * 4
end

It creates function times_6, for example, if I pass 6 as a macro parameter.

Now what I don't understand: Here we unquote the atom :"times_#{n}". Elixir docs say that when you quote an atom it returns an atom. So when I unquote an atom, I should get this atom back too. And this is true:

iex(15)> Macro.to_string quote do: unquote(:"times_6")
":times_6"

But using () right after the quote gives this:

iex(14)> Macro.to_string quote do: unquote(:"times_6")()
"times_6()"

An atom with parentheses suddenly becomes not an atom. And if I substitute unquote(:"times_6") with :"times_6" it doesn't work:

iex(4)> Macro.to_string quote do: :"times_6"()
** (SyntaxError) iex:4: syntax error before: '('

Please, what is going on, I don't get it

1
Shouldn't it be do: a * unquote(n) instead of a * 4 ?Sheharyar
Yes. It should. But this is not the point. This macro works fine. I just try to understand why there is difference with parentheses. Because at first I didn't know that quoting/unquoting atom with parentheses suddenly returns you not an atom.airled
The issue here isn't with the macro, it's with :"times_6"(). That itself is an invalid statementSheharyar
Yes. But why is unquote(:"times_6")() correct? unquote(:"times_6") should return you :"times_6"airled

1 Answers

4
votes

That's how quote & unquote are implemented in Elixir

From the Elixir Metaprogramming Guide:

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:

iex> quote do: sum(1, 2, 3)
{: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.


Module and Method names are internally represented as atoms in elixir. Looking directly at the underlying elixir data generated when calling unquote(:"hello") and unquote(:"hello")() might help clear this up:

iex(27)> quote do: unquote(:"hello")   
:hello

iex(28)> quote do: unquote(:"hello")() 
{:hello, [], []}

The first one simply returns an atom while the second one returns an Elixir Data Structure (a tuple composed of 3 elements) which represents a function call to the hello method with 0 arguments. The unquote(:"hello")() is transformed into hello() which is then used as a method.