如何在宏中获取未加引号/扩展参数的值

时间:2017-05-15 06:05:16

标签: macros elixir metaprogramming

我需要在@attr

的调用中访问A.target的真实价值
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
  • 的任何变体

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

3 个答案:

答案 0 :(得分:1)

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

的上下文中执行

答案 1 :(得分:0)

有点迟到了这个问题;编写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

答案 2 :(得分:-1)

这不是你想要的吗?

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