对动态生成的宏进行双重取消引用

Ben*_*Hao 1 macros metaprogramming elixir

鉴于以下内容:

  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
Run Code Online (Sandbox Code Playgroud)

是什么原因fn_name = unquote(fn_name)?如果我省略这一行,那就是编译错误.这种"双重"不引用的原因是什么?

Jos*_*lim 8

让我们简化一下这个例子:

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
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,因为quote返回一个带有两个不带引号的元素的元组,它相当于:

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
Run Code Online (Sandbox Code Playgroud)

现在更容易理解如果unquote(fn_name)之前没有发生的情况:变量fn_name根本不存在于宏定义中.请记住,所有defs(def,defp,defmacro等)都会启动一个新的变量作用域,因此如果要在内部使用fn_name,则需要以某种方式定义它.

我们在这段代码中看到的另一个属性是Elixir在看到它时会停止取消引用quote.因此,在上面的引用中,unquote在定义宏时不会取消引用,而是在宏执行时不会引用,这也解释了为什么需要在宏内部定义变量.