`unquote` 和 `binding_quote` 在 case 表达式中
`unquote` and `binding_quote` in case expression
这个有效:
defmacro returning(expr, do: block) do
quote do
case unquote(expr), do: unquote(block)
end
end
但事实并非如此:
defmacro returning(expr, do: block) do
quote bind_quoted: [expr: expr, block: block] do
case expr, do: block
end
end
为什么?
这是关于上下文的。以下代码等效于使用 bind_quoted
:
的代码
defmacro returning(expr, do: block) do
quote do
expr = unquote(expr)
block = unquote(block)
case expr, do: block
end
end
假设我们这样写:
returning(true) do
true -> :ok
false -> :not_ok
end
这会生成以下 AST:
{:__block__, [],
[{:=, [], [{:expr, [], Test}, true]},
{:=, [], [{:block, [], Test}, [{:->, [line: 46], [[true], :ok]}, {:->, [line: 47], [[false], :not_ok]}]]},
{:case, [], [{:expr, [], Test}, [do: {:block, [], Test}]]}]}
这个元组的工作方式如下:
{<name of the function>, <context>, <arguments>}
- 在第二行中,您会看到
{:=, [], [{:expr, [], Test}, true]}
,相当于 expr = true
。这不会引发任何错误。
- 第三行有点复杂。第一部分是变量绑定
block = <something>
。 <something>
在这种情况下,Elixir 将 true -> :ok
和 false -> :not_ok
子句解释为函数 ->([true], :ok)
和 ->([false], :not_ok)
,尝试计算它们但没有找到它们。
另一个不会失败的宏,对于相同的代码,您将获得以下 AST:
{:case, [],
[true,
[do: [{:->, [line: 42], [[true], :ok]},
{:->, [line: 43], [[false], :not_ok]}]]]}
在案例的上下文中,:->
被解释为 case
语句的子句,而不是函数。在 case
声明之前没有变量绑定,因此对于编译器来说一切都是找到的。
这就是 bind_quoted
在这种情况下不起作用的原因。仅仅是因为不知道上下文。
我希望这能回答你的问题。
PS:
我使用以下代码来检查宏 AST 中的差异:
defmodule Test do
defmacro good_returning(expr, do: block) do
ast = quote do
case unquote(expr), do: unquote(block)
end
IO.inspect ast
:ok
end
defmacro bad_returning(expr, do: block) do
ast = quote bind_quoted: [expr: expr, block: block] do
case expr, do: block
end
IO.inspect ast
:ok
end
end
import Test
good_returning(true) do
true -> :ok
false -> :not_ok
end
bad_returning(true) do
true -> :ok
false -> :not_ok
end
这个有效:
defmacro returning(expr, do: block) do
quote do
case unquote(expr), do: unquote(block)
end
end
但事实并非如此:
defmacro returning(expr, do: block) do
quote bind_quoted: [expr: expr, block: block] do
case expr, do: block
end
end
为什么?
这是关于上下文的。以下代码等效于使用 bind_quoted
:
defmacro returning(expr, do: block) do
quote do
expr = unquote(expr)
block = unquote(block)
case expr, do: block
end
end
假设我们这样写:
returning(true) do
true -> :ok
false -> :not_ok
end
这会生成以下 AST:
{:__block__, [],
[{:=, [], [{:expr, [], Test}, true]},
{:=, [], [{:block, [], Test}, [{:->, [line: 46], [[true], :ok]}, {:->, [line: 47], [[false], :not_ok]}]]},
{:case, [], [{:expr, [], Test}, [do: {:block, [], Test}]]}]}
这个元组的工作方式如下:
{<name of the function>, <context>, <arguments>}
- 在第二行中,您会看到
{:=, [], [{:expr, [], Test}, true]}
,相当于expr = true
。这不会引发任何错误。 - 第三行有点复杂。第一部分是变量绑定
block = <something>
。<something>
在这种情况下,Elixir 将true -> :ok
和false -> :not_ok
子句解释为函数->([true], :ok)
和->([false], :not_ok)
,尝试计算它们但没有找到它们。
另一个不会失败的宏,对于相同的代码,您将获得以下 AST:
{:case, [],
[true,
[do: [{:->, [line: 42], [[true], :ok]},
{:->, [line: 43], [[false], :not_ok]}]]]}
在案例的上下文中,:->
被解释为 case
语句的子句,而不是函数。在 case
声明之前没有变量绑定,因此对于编译器来说一切都是找到的。
这就是 bind_quoted
在这种情况下不起作用的原因。仅仅是因为不知道上下文。
我希望这能回答你的问题。
PS:
我使用以下代码来检查宏 AST 中的差异:
defmodule Test do
defmacro good_returning(expr, do: block) do
ast = quote do
case unquote(expr), do: unquote(block)
end
IO.inspect ast
:ok
end
defmacro bad_returning(expr, do: block) do
ast = quote bind_quoted: [expr: expr, block: block] do
case expr, do: block
end
IO.inspect ast
:ok
end
end
import Test
good_returning(true) do
true -> :ok
false -> :not_ok
end
bad_returning(true) do
true -> :ok
false -> :not_ok
end