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
quote
& unquote
are implemented in ElixirFrom 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.