递归宏药剂

时间:2020-02-10 13:43:35

标签: macros 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

当我了解到两种语言的知识时,我很高兴能对此有所了解!

2 个答案:

答案 0 :(得分:2)

TL; DR:这是不可能的。


中的宏不是您所期望的。当编译器看到宏时,它会在编译期间对其进行调用 注入它返回的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]),以便编译器知道该列表的大小和元素。

顺便说一句,我使用了主体优化的递归,但是您可以轻松地添加累加器并将其转换为尾部优化的递归。

相关问题