【问题标题】:How to get an unquoted/expanded parameter’s value inside a macro如何在宏中获取未引用/扩展参数的值
【发布时间】:2017-05-15 06:05:16
【问题描述】:

我需要在对 A.target 的调用中访问 @attr 的实际值

defmodule A do
  defmacro target(param) do
    IO.inspect param, label: "Inside a macro"
  end
end

defmodule B do
  @attr 42
  require A
  A.target(@attr)
end

我肯定有

Inside a macro: {:@, [line: 10], [{:attr, [line: 10], nil}]}

已打印,因为此时正在构建 AST,而A.target/1 不负责扩展其参数。我发现我可以访问参数的值,而不是 quote do 块中的 AST:

defmodule A do
  defmacro target(param) do
    quote do: IO.inspect uquote(param), label: "unquote"
  end
end

问题是我需要预先扩展价值。我尝试过的:

  • var!
  • Macro.expand
  • Code.eval 的任何变体

所以,我的问题是:是否可以在宏主体的第一行强制扩展宏参数

【问题讨论】:

    标签: macros elixir metaprogramming


    【解决方案1】:

    我不相信你可以。在宏中的引号块之外,@attr 没有值。它只是一个引用的表达式。模块 B 尚未编译。您只能在 A 的引用块内访问 @attr,因为它是在 B 的上下文中执行的。

    【讨论】:

    • 这听起来很合理,我的第一个猜测就是这个。另一方面,应该可以推迟 AST 的扩展以允许例如迭代传递给宏的参数/基于一些尚未扩展的配置构建生成的 AST。
    • 可以,但只有在宏的引用块内取消引用它时它才有效。你能解释一下你想做什么吗?
    • 我正在尝试向通过一组宏传递未引用值的库提供拉取请求。我只是试图通过不触及现有代码来最小化这个 PR 的大小,但这似乎是不可能的。
    【解决方案2】:

    这个问题有点晚了;不熟悉编写 elixir 宏。

    我不建议这样编程,但这是一个有趣的练习!

    您的问题似乎是关于使用常量@attr。

    这是这个问题的主要痛点!

    绝对是一个 hack ?,但您可以通过两个更改来转换。

    1) 将元组从宏转换为列表。

    2) 宏中的attr 效果不好,需要改成str。

    这里的第二个特定问题是 eval 再次出现的错误,即使您在模块内调用它也可能会访问某个宏模块。

    ** (ArgumentError) cannot invoke @/1 outside module
        (elixir) lib/kernel.ex:5212: Kernel.assert_module_scope/3
        (elixir) expanding macro: Kernel.@/1
        nofile:11: (file)
        expanding macro: A.target/1
        try.exs:11: B (module)
    

    通过一些巧妙的操作来操纵值来做任何你想做的事情,只需确保在取消引用之前将列表转换回元组并将“attr”转换为 {attr}。

    解决方案:

    defmodule A do
      def foo( {item1, item2, [ {item4, item5, item6} ] } ) do
        [item1, item2, "#{item4}", item5, item6]
      end
      defmacro target(param) do
        macroModified =  param |> foo()
        quote do
          param = unquote(param)
          macroModified  = unquote(macroModified )
    
          IO.inspect [result: param, macro: [macroModified ]]
        end
      end
    end
    
    defmodule B do
      @attr 42
      require A
      A.target(@attr)
    end
    

    【讨论】:

      【解决方案3】:

      这不是你想要的吗?

      defmodule A do
        defmacro target(param) do
          quote do
            IO.inspect unquote(param), label: "Inside a macro"
          end
        end
      end
      
      defmodule B do
        @attr 42
        require A
        A.target(@attr)
      end
      

      宏接收带引号的参数。要获得实际值,您必须取消引用它。您只能在引用块内取消引用参数。

      当一个宏返回一个带引号的表达式时,它被插入到调用模块中并运行,因此知道IO.inspect...B模块中运行。

      【讨论】:

        猜你喜欢
        • 2020-05-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-29
        • 2016-05-14
        • 1970-01-01
        • 1970-01-01
        • 2016-02-13
        相关资源
        最近更新 更多