我正在使用Elixir宏-特别是自称为宏的宏,这是我在Scheme中经常做的事情。我在下面创建了一个小测试宏,但是它只是挂了iex-没有任何内容打印到控制台。有谁知道为什么以及可以采取什么措施来纠正它?
defmodule RecMac do
defmacro test_rec(x) do
quote do
IO.puts("Started?")
if(unquote(x) < 1) do
IO.puts("Done?")
"done"
else
IO.puts("Where are we")
IO.puts(unquote(x))
RecMac.test_rec(unquote(x) - 1)
end
end
end
end
编辑!
好,事实证明,您可以定义在结构上要匹配的递归宏(例如列表)。以下为我工作。并在下面确认@Aleksei Matiushkin,以上方法将无法使用,并且确实无法在计划中使用!
defmacro test_rec([h | t]) do
quote do
IO.inspect([unquote(h) | unquote(t)])
RecMac.test_rec(unquote(t))
end
end
defmacro test_rec([]) do
quote do
IO.puts "Done"
end
end
end
当我了解到两种语言的知识时,我很高兴能对此有所了解!
答案 0 :(得分:2)
TL; DR:这是不可能的。
elixir中的宏不是您所期望的。当编译器看到宏时,它会在编译期间对其进行调用 注入它返回的AST 并将其替换为调用宏的位置。也就是说,递归宏在编译阶段始终会导致无限循环。
将IO.puts("something")
放在之前 quote do
指令中,您将看到它被无限打印,每次后续调用以展开宏。
您可以使用@compile {:inline, test_rec: 1}
来实现您想要的行为。为了更好地理解宏,您可能应该阅读 Elixir指南中的Macros
部分,尤其是以下摘录:
宏比普通的Elixir函数更难编写,并且在不需要它们时认为使用宏是不好的样式。因此,负责任地编写宏。
答案 1 :(得分:2)
实际上,您可以进行某种递归,但是重点是要考虑要避免在quote do [....] end
中调用宏。您的IO.puts示例就是这样
defmodule RecMac do
defmacro test_rec(list) do
{:__block__, [], quote_value(list)}
end
defp quote_value([]), do: []
defp quote_value([h | t]) do
if h < 1 do
[]
else
ast = quote do
IO.puts("#{unquote(h)}")
end
[ast | quote_value(t)]
end
end
end
您会注意到两件事,我确实在宏中使用了递归,但是使用私有函数quote_value/1
在外部引用中使用了该函数,并且该函数具有在发现值小于1
之后会“停止”的逻辑。所有“引号”都放入列表中,技巧是将该列表放入元组{:__block__, [], put_quote_list_here}
现在请注意,如果不预先知道test_rec的列表参数(在编译期间),则此宏不会编译,因此您需要调用宏test_rec(["a", "b", 0, 100, 200])
,以便编译器知道该列表的大小和元素。
顺便说一句,我使用了主体优化的递归,但是您可以轻松地添加累加器并将其转换为尾部优化的递归。