表达式中的`unquote`和`binding_quote`

时间:2016-07-12 08:29:19

标签: metaprogramming elixir

这有效:

1: kd> !process fffffa8014c25170 2
PROCESS fffffa8014c25170
    SessionId: 1  Cid: 0ad4    Peb: 7fffffdf000  ParentCid: 07b8
    DirBase: 2b451000  ObjectTable: fffff8a002e61620  HandleCount:  12.
    Image: exe-once.exe

        THREAD fffffa8013958b60  Cid 0ad4.0724  Teb: 000007fffffdd000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Non-Alertable
            fffffa8013aa1060  Thread

        THREAD fffffa8013aa1060  Cid 0ad4.01e8  Teb: 000007fffffdb000 Win32Thread: 0000000000000000 WAIT: (DelayExecution) KernelMode Non-Alertable
            fffffa8013aa1420  Semaphore Limit 0x1

但这不是:

defmacro returning(expr, do: block) do
  quote do
    case unquote(expr), do: unquote(block)
  end
end

为什么?

1 个答案:

答案 0 :(得分:3)

关于背景。以下代码等同于使用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