Elixir宏,引用和不引用

时间:2016-08-21 16:17:44

标签: elixir

我正在学习Elixir并尝试使用Macro系统 我试图使一个变量可用于一个宏,它将被其他代码包裹起来,但我显然做错了。

这是我的起点宏,它有效。我知道有更多惯用的方法可以在Elixir中进行迭代,我把它写成一个练习:

defmodule Loops do
  defmacro times(n, [do: block]) do
    quote do
      Enum.each(1..unquote(n), fn(_) -> unquote(block) end)
    end
  end
end

import Loops

times 3 do
  IO.puts "Hello!"
end

现在,我想要完成的是能够从do块引用计数器。所以,如果我给fn参数指定一个正确的名称:

Enum.each(1..unquote(n), fn(_) -> unquote(block) end)
# becomes:
Enum.each(1..unquote(n), fn(counter) -> unquote(block) end)

我希望能够做到这样的事情:

times 3 do
  IO.puts "Hello! iteration n: #{counter}"
end

但由于CompileError,这不起作用并引发undefined function counter/0。从我传递给宏调用do块引发了错误,我觉得这有点违反直觉,因为我认为该块将放置调用unquote()时扩展代码。

我是以错误的方式解决问题,还是这根本不可能?

1 个答案:

答案 0 :(得分:3)

您可以使用Kernel.var!/2使counter成为“不卫生”变量。这将确保它可用于生成的代码,而无需由Elixir的宏系统重命名。

defmodule Loops do
  defmacro times(n, [do: block]) do
    quote do
      Enum.each(1..unquote(n), fn(var!(counter)) ->
        unquote(block)
      end)
    end
  end
end

defmodule Main do
  require Loops

  def main do
    Loops.times 3 do
      IO.puts "Hello! iteration n: #{counter}"
    end
  end
end

Main.main

输出:

Hello! iteration n: 1
Hello! iteration n: 2
Hello! iteration n: 3