【问题标题】:Invoke a private macro within a quote block在引用块中调用私有宏
【发布时间】:2018-11-02 03:30:16
【问题描述】:

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

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)

【问题讨论】:

    标签: macros elixir metaprogramming


    【解决方案1】:

    这里的问题在于嵌套引用:私有宏应该返回双引号表达式(因为从外部范围调用它需要显式地unquote,并且宏仍然需要返回一个带引号的表达式。)

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

    另一方面,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 核心的一个很好的功能请求,允许禁用附加引用的选项。


    旁注 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.)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-03-11
      • 2016-07-24
      • 2019-09-13
      • 2015-03-19
      • 1970-01-01
      相关资源
      最近更新 更多