繁体   English   中英

递归宏 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:这是不可能的。


在宏是不是你所期望的是什么。 当编译器看到一个宏,它在编译时调用它注入它在那里它被称为地方返回的AST。 也就是说,递归宏总是会导致编译阶段的无限循环。

IO.puts("something")放在quote do指令之前,您会看到它被无限打印,每次后续调用扩展宏时都会打印一次。

您可以使用@compile {:inline, test_rec: 1}来实现您所追求的行为。 为了更好地理解宏,您可能应该阅读Elixir 指南中的Macros部分,特别是以下摘录:

宏比普通的 Elixir 函数更难编写,并且在不必要时使用它们被认为是不好的风格。 因此,请负责任地编写宏。

实际上你可以做一些递归,但重点是想想你在做什么以避免在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])以便编译器知道大小和该列表的元素。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM