简体   繁体   中英

Double unquoting for dynamically generated macros

Given the following:

  for fn_name <- [:foo, :bar, :baz] do
    defmacro unquote(fn_name)(do: inner) do
      fn_name = unquote(fn_name) # <--- Why?
      quote do
        IO.puts "#{unquote(fn_name)} called"
        unquote(inner)
      end
    end
  end

What's the reason for fn_name = unquote(fn_name) ? If I omit this line, it's a compile error. What's the reason for this "double" unquoting?

Let's simplify the example a little bit:

for fn_name <- [:foo, :bar, :baz] do
  defmacro unquote(fn_name)(do: inner) do
    fn_name = unquote(fn_name) # <--- Why?
    quote do
      {unquote(fn_name), unquote(inner)}
    end
  end
end

In the example above, because quote is returning a tuple with two unquoted elements, it is equivalent to:

for fn_name <- [:foo, :bar, :baz] do
  defmacro unquote(fn_name)(do: inner) do
    fn_name = unquote(fn_name) # <--- Why?
    {fn_name, inner}
  end
end

Now it is easier to understand what happens if you don't unquote(fn_name) before: the variable fn_name simply wouldn't exist inside the macro definition. Remember that all def s (def, defp, defmacro, etc) start a new variable scope, so if you want to use fn_name inside, you need to define it somehow.

The other property we are seeing in this code is that Elixir will stop unquoting when it sees a quote . So in the quote above, unquote won't be unquoted when the macro is defined but rather when the macro is executed, which also explains why the variable is required to be defined inside the macro.

It's because of Hygiene .

Elixir has the concept of macro hygiene . Hygiene means that variables, imports, and aliases that you define in a macro do not leak into the caller's own definitions.

for fn_name <- [:foo, :bar, :baz] do
    defmacro unquote(fn_name)(do: inner) do
      fn_name = unquote(fn_name) # <-- This is macro's context
      quote do
        IO.puts "#{unquote(fn_name)} called" # <-- This is caller's context
        unquote(inner)
      end
    end
  end

You should read Hygiene Protects the Caller's Context from Chris McCord's Metaprogramming Elixir book

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM