Macros 递归宏长生不老药

Macros 递归宏长生不老药,macros,elixir,Macros,Elixir,我在玩弄长生不老药宏——特别是那些自称的宏,这是我在Scheme中经常做的事情。我在下面创建了一个小测试宏,但它只是挂起iex——控制台上没有打印任何内容。有没有人能洞察为什么会这样,以及可以采取什么措施来纠正它 defmac-do模块 defmacro test_recx do 引述 伊奥·普茨开始了吗? ifunquotex

我在玩弄长生不老药宏——特别是那些自称的宏,这是我在Scheme中经常做的事情。我在下面创建了一个小测试宏,但它只是挂起iex——控制台上没有打印任何内容。有没有人能洞察为什么会这样,以及可以采取什么措施来纠正它

defmac-do模块 defmacro test_recx do 引述 伊奥·普茨开始了吗? ifunquotex<1 do IO.putsDone? 完成 其他的 我们到了 IO.putsunquotex RecMac.test_recunquetotex-1 终止 终止 终止 终止 编辑

好的,所以你可以定义递归宏,其中有一个结构上的差异来匹配eg列表。以下是我的工作。为了确认下面的@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;医生:这是不可能的

中的宏不是您所期望的。当编译器看到一个宏时,它会在编译期间调用它,并在调用它的地方注入它返回的AST。也就是说,递归宏在编译阶段总是导致无限循环

将IO.putsomething放在quote do指令之前,您将看到它被无限打印,每次后续调用一次以展开宏

您可以使用@compile{:inline,test_rec:1}来实现您所追求的行为。为了更好地理解宏,您可能应该阅读《长生不老药指南》中的一节,尤其是本摘录:

宏比普通的长生不老药函数更难编写,在不需要宏的时候使用宏被认为是不好的风格。因此,负责任地编写宏

TL;医生:这是不可能的

中的宏不是您所期望的。当编译器看到一个宏时,它会在编译期间调用它,并在调用它的地方注入它返回的AST。也就是说,递归宏在编译阶段总是导致无限循环

将IO.putsomething放在quote do指令之前,您将看到它被无限打印,每次后续调用一次以展开宏

您可以使用@compile{:inline,test_rec:1}来实现您所追求的行为。为了更好地理解宏,您可能应该阅读《长生不老药指南》中的一节,尤其是本摘录:

宏比普通的长生不老药函数更难编写,在不需要宏的时候使用宏被认为是不好的风格。因此,负责任地编写宏


实际上,您可以执行某种递归,但关键是要考虑您正在做什么,以避免在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后具有停止的逻辑。所有的引号都放在列表中,诀窍是把这个列表放在元组{:{:}块{,[],放在这里}

现在请注意,如果test_rec的list参数在编译时不预先知道,则该宏不会编译,因此需要调用宏test_rec[a,b,0,100,200],以便编译器知道该列表的大小和元素


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

实际上您可以执行某种递归,但重点是要考虑您正在做什么,以避免在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后具有停止的逻辑。所有的引号都放在列表中,诀窍是把这个列表放在元组{:{:}块{,[],放在这里}

现在请注意,如果test_rec的list参数在编译时不预先知道,则该宏不会编译,因此需要调用宏test_rec[a,b,0,100,200],以便编译器知道该列表的大小和元素


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

感谢您的回答,这在Elixir中是不可能的,但这在Scheme中肯定是可能的,这使我认为这是Elixir宏的一个缺点:-首先不是编译语言,应该称为transpilers。Elixir编译成BEAM,并发明了宏来简化AST的遍历。这是一个特点,决不是缺点。如果您觉得宏应该始终像scheme中的宏一样,请随意将它们称为ast注入器,而不是宏。当然是汇编的。不过,我不想在这里争论:-再次感谢你的回答-不能在长生不老药中做到这一点不是交易
对我来说,这种语言在其他方面很有效率,也很有趣。谢谢你的回答,这在Elixir中是不可能的,但在Scheme中肯定是可能的,这让我觉得这是Elixir宏的一个缺点:-首先不是编译语言,比如应该称为transpilers。Elixir编译成BEAM,并发明了宏来简化AST的遍历。这是一个特点,决不是缺点。如果您觉得宏应该始终像scheme中的宏一样,请随意将它们称为ast注入器,而不是宏。当然是汇编的。但不是在这里争论:-再次感谢你的回答-不能用长生不老药来做这件事对我来说不是一个破坏者,语言在其他方面是非常有效的,它很有趣。他最好根本不用这个,我想。@Milan Jaric感谢你提供了一段非常有趣的代码来思考!我只是看到了Elixir宏可以实现什么和不可以实现什么,所以这很好:-@Daniel我同意,因为它与在运行时直接调用quote_值没有任何区别。但是,如果你有更复杂的东西,例如,如果你想为某个领域构建DSL,那么做这样的事情是有意义的,而不是使用IO.puts,只需添加应该可以工作的代码,而不是DSL关键字。我想,她最好不要使用这个。米兰·贾里奇感谢你提供了一段非常有趣的代码来思考!我只是看到了Elixir宏可以实现什么和不可以实现什么,所以这很好:-@Daniel我同意,因为它与在运行时直接调用quote_值没有任何区别。但是如果你有更复杂的东西,例如,如果你想为某个领域构建DSL,那么做这样的事情是有意义的,而不是使用IO.puts,只需添加可以工作的代码,而不是DSL关键字