递归宏 Elixir
Recursive macro Elixir
我正在玩弄 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
我很高兴能够深入研究这门课程,因为我学到了两种语言!
TL;DR: 这是不可能的。
elixir 中的宏与您期望的不同。当编译器看到宏时,它会在编译期间调用它 和 注入它返回的 AST在它被调用的地方。也就是说,递归宏总是会导致编译阶段的无限循环。
将 IO.puts("something")
放在 quote do
指令之前,您将看到它被无限打印,每次后续调用一次以展开宏。
您可以使用 @compile {:inline, test_rec: 1}
来实现您想要的行为。为了更好地理解宏,您可能应该阅读 Elixir 指南 中的 Macros
部分,特别是以下摘录:
Macros are harder to write than ordinary Elixir functions and it’s considered to be bad style to use them when they’re not necessary. So write macros responsibly.
实际上你可以做一些递归,但要点是想想你在做什么以避免在 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
的值后具有 "stops" 的逻辑.所有 "quotes" 都放入列表中,技巧是将此列表放入元组 {:__block__, [], put_quote_list_here}
现在请注意,如果 test_rec 的列表参数事先(在编译期间)未知,则此宏不会编译,因此您需要调用宏 test_rec(["a", "b", 0, 100, 200])
以便编译器知道其大小和元素列表。
顺便说一句,我使用了体优化递归,但您可以轻松添加累加器并将其转换为尾优化递归。
我正在玩弄 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
我很高兴能够深入研究这门课程,因为我学到了两种语言!
TL;DR: 这是不可能的。
elixir 中的宏与您期望的不同。当编译器看到宏时,它会在编译期间调用它 和 注入它返回的 AST在它被调用的地方。也就是说,递归宏总是会导致编译阶段的无限循环。
将 IO.puts("something")
放在 quote do
指令之前,您将看到它被无限打印,每次后续调用一次以展开宏。
您可以使用 @compile {:inline, test_rec: 1}
来实现您想要的行为。为了更好地理解宏,您可能应该阅读 Elixir 指南 中的 Macros
部分,特别是以下摘录:
Macros are harder to write than ordinary Elixir functions and it’s considered to be bad style to use them when they’re not necessary. So write macros responsibly.
实际上你可以做一些递归,但要点是想想你在做什么以避免在 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
的值后具有 "stops" 的逻辑.所有 "quotes" 都放入列表中,技巧是将此列表放入元组 {:__block__, [], put_quote_list_here}
现在请注意,如果 test_rec 的列表参数事先(在编译期间)未知,则此宏不会编译,因此您需要调用宏 test_rec(["a", "b", 0, 100, 200])
以便编译器知道其大小和元素列表。
顺便说一句,我使用了体优化递归,但您可以轻松添加累加器并将其转换为尾优化递归。