在引用块中调用私有宏

时间:2018-05-23 08:22:59

标签: macros elixir metaprogramming

我正在尝试使用代码块本身内定义的变量在引用块内调用私有宏。 这是伪代码,显示了我想做的事情(不起作用)

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 个答案:

答案 0 :(得分:1)

这里的问题是嵌套引用:私有宏应该返回双引号表达式(因为要从外部作用域调用它需要显式unquote,并且宏仍然需要返回引用的表达式。)

旁注:您的更新部分错误;您可能会注意到,在编译阶段打印"hello",即编译use Foo时。这是因为需要双引号,当IO.puts宏中的unquote满足时,更新部分中的代码才会执行​​__using__

另一方面,my只应引用一次。这可以通过明确引用 AST 来实现,将msg传递给

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来调整嵌套引号中的 unquoting ,而我们需要完全相反的。

下面的

旁注:不起作用,我希望这是Kernel.SpecialForms.quote/2实施中的错误。

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

FWIW:我filed an issue

我认为这可能是对Elixir核心的一个很好的功能请求,允许一个禁用其他引用的选项

Sidenote 2:以下作品(最简洁的方法):

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

因此,您可以避免使用明确的AST并使用上述内容。我正在离开答案,因为直接处理AST也是一个非常好的选择,应该用作大锤/最后的手段,它总能工作。

如果IO.puts不是您想要的目标,您可以致电quote do: YOUR_EXPR,了解您希望在debug宏中添加的内容:

quote do: to_string(arg)
#⇒ {:to_string, [context: Elixir, import: Kernel], [{:arg, [], Elixir}]}

并在结果中手动取消引用arg

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

这基本上就是我获得原始请求的AST(IO.puts。)