在引用块中调用私有宏

3

我想在引用块内部使用代码块内定义的变量来调用一个私有宏。以下是伪代码(无法正常工作):

defmodule Foo do
  defmacrop debug(msg) do
    quote bind_quoted: [msg: msg], do: IO.puts(msg)
  end

  defmacro __using__(_) do
    quote do
      def hello do
        my = "testme"

        unquote(debug(quote do: my))
      end
    end
  end
end

defmodule Bar do
  use Foo
end

Bar.hello()

而这会在编译时被转换(在我的脑海中)为:

defmodule Bar do
  def hello do
    my = "testme"
    IO.puts(my)
  end
end

有没有办法实现这个?我在寻找相关文档方面遇到了困难。
更新
我发现:
defmodule Foo do
  defmacrop debug() do
    quote do: IO.puts("hello")
  end

  defmacro __using__(_) do
    quote do
      def hello do
        my = "testme"

        unquote(debug())
      end
    end
  end
end

这段内容的意思是要将变量正确地转换成我需要的形式,但我在努力寻找一种方法来传递变量本身,以便它变成IO.puts(my)

1个回答

1
问题在于嵌套引用:私有宏应该返回双引号表达式(因为要从外部范围调用它需要明确地解引用,而宏仍然期望返回一个引用表达式)。
附注:您的更新部分是错误的;您可能会注意到,在编译阶段打印“hello”,即当编译“use Foo”时。这是因为需要双引号,您的更新部分中的代码在遇到“__using__”宏中的“unquote”时执行“IO.puts”。
另一方面,“my”只应引用一次。这可以通过显式引用AST来实现,将“msg”传递给其中“as is”。
defmodule Foo do
  defmacrop debug(msg) do
    quote bind_quoted: [msg: msg] do
      {
        {:., [], [{:__aliases__, [alias: false], [:IO]}, :puts]},
        [],
        [msg]} # ⇐ HERE `msg` is the untouched argument
    end 
  end 

  defmacro __using__(_) do
    quote do
      def hello do
        my = "testme"

        unquote(debug(quote do: my))
      end 
    end 
  end 
end

defmodule Bar do
  use Foo 
end

Bar.hello()
#⇒ "testme"

我无法通过对Kernel.SpecialForms.quote/2的调用中使用选项来实现相同的功能;唯一可用的相关选项是unquote,用于调整嵌套引号内部的取消引用,而我们需要完全相反的效果。


附注:下面的代码不起作用,我认为这是Kernel.SpecialForms.quote/2实现中的一个错误。

quote bind_quoted: [msg: msg] do
  quote bind_quoted: [msg: msg], do: IO.puts(msg)
end

FWIW:我提交了一个问题

我认为向Elixir核心提出一个禁用额外引用的选项可能是一个好的功能请求。


附注2:以下方法最简洁(most concise approach):

defmacrop debug(msg) do
  quote bind_quoted: [msg: msg] do
    quote do: IO.puts(unquote msg)
  end
end

因此,你可以避免使用显式的AST,只需使用上述方法即可。我保留答案不变,因为直接处理AST也是一个非常好的选择,应该作为一个大锤/最后的手段使用,这总是有效的。
如果 IO.puts 不是你想要的目标,你可以在你想要在 debug 宏中使用的内容上调用 quote do: YOUR_EXPR
quote do: to_string(arg)
#⇒ {:to_string, [context: Elixir, import: Kernel], [{:arg, [], Elixir}]}

并手动取消结果中的arg引用:

#                                             ✗  ⇓⇓⇓ {:arg, [], Elixir} 
#                                             ✓  ⇓⇓⇓ arg
{:to_string, [context: Elixir, import: Kernel], [arg]}

这基本上是我获取您原始请求(IO.puts)的AST的方式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接