在具有修改列表的宏中使用 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
到目前为止我已经尝试了很多东西,例如:
- 使用嵌套引号
- 使用
bind_quoted
- 浏览
Macro
和 Code
文档
但没有任何效果,我无法弄清楚我做错了什么。
当 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,因为 elixir 尝试局部变量,如果在当前上下文中找不到它,它会尝试调用使用此名称和元数为零的函数。
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
到目前为止我已经尝试了很多东西,例如:
- 使用嵌套引号
- 使用
bind_quoted
- 浏览
Macro
和Code
文档
但没有任何效果,我无法弄清楚我做错了什么。
当 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,因为 elixir 尝试局部变量,如果在当前上下文中找不到它,它会尝试调用使用此名称和元数为零的函数。