`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 -> :okfalse -> :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