在具有修改列表的宏中使用 unquote_splicing

Using unquote_splicing in macros with modified lists

Elixir 的 unquote_splicing 在直接取消引用传递的列表时没有问题。例如调用下面的宏Test1.wrap([1,2,3])会正确return[0,0,0,1,2,3,0,0,0].

defmodule Test1 do
  defmacro wrap(nums) do
    quote do
      [0,0,0, unquote_splicing(nums), 0,0,0]
    end
  end
end

但是如果我对列表进行任何更改然后尝试调用 unquote_splicing,Elixir 甚至不会让我定义宏:

defmodule Test2 do
  defmacro double_wrap(nums) do
    quote do
      doubles = Enum.map(unquote(nums), & &1*2)

      [0,0,0, unquote_splicing(doubles), 0,0,0]
    end
  end
end

这将直接引发编译错误:

warning: variable "doubles" does not exist and is being expanded to "doubles()", please use parentheses to remove the ambiguity or change the variable name
  iex:37: Test.double_wrap/1

** (CompileError) iex:37: undefined function doubles/0
    (elixir) src/elixir_locals.erl:108: :elixir_locals."-ensure_no_undefined_local/3-lc$^0/1-0-"/2
    (elixir) src/elixir_locals.erl:108: anonymous fn/3 in :elixir_locals.ensure_no_undefined_local/3

到目前为止我已经尝试了很多东西,例如:

但没有任何效果,我无法弄清楚我做错了什么。

quote 块内定义了 doubles 时,无需到达 quote 块之外来检索 doubles 的值。在引用块内定义的变量会自动将它们的值嵌入到 AST 中。因此,您可以使用函数 List.flatten():

defmodule A do
  defmacro double_wrap(nums) do
    quote do
      doubles = Enum.map(unquote(nums), & &1*2)

      List.flatten [0,0,0, doubles, 0,0,0]
    end
  end
end

在 iex 中:

~/elixir_programs$ iex
Erlang/OTP 20 [erts-9.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.8.2) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> c "a.ex"
[A]

iex(2)> require A
A

iex(3)> A.double_wrap [1, 2, 3]
[0, 0, 0, 2, 4, 6, 0, 0, 0]

iex(4)> 

宏返回的内容直接注入到调用代码的位置。 Kernel.SpecialForms.unquote/1(以及 unquote_splicing/1)用于访问调用者上下文。这就是您的代码引发的原因:调用者上下文中没有定义局部变量 doubles

您可以做的是在 quote 块之外声明 doubles

defmodule D do
  defmacro double_wrap(nums) do
    doubles = Enum.map(nums, & &1*2)
    quote do
      [0,0,0, unquote_splicing(doubles), 0,0,0]
    end
  end
end

require D
D.double_wrap [1,2,3]
#⇒ [0, 0, 0, 2, 4, 6, 0, 0, 0]

也就是说,这很愉快地解决了:

doubles = [1,2,3]
quote do: [0,0,0, unquote_splicing(doubles), 0,0,0]
#⇒ [0, 0, 0, 1, 2, 3, 0, 0, 0]

而这不是,因为调用者上下文中没有 doubles

quote do
  doubles = [1,2,3]
  [0,0,0, unquote_splicing(doubles), 0,0,0]
end
#⇒ ☠️  ** (CompileError) iex:7: undefined function doubles/0

错误消息说 undefined function,因为 尝试局部变量,如果在当前上下文中找不到它,它会尝试调用使用此名称和元数为零的函数。