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