2016-08-21 51 views
0

我正在学习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()时,该块将是放置在的扩展码中。

我是以错误的方式接近问题的,还是这是不可能的?

回答

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 
+0

谢谢,这正是我需要的。 – tompave