使用bind_quoted语句获得意外结果

时间:2016-04-12 07:08:52

标签: elixir

我正在尝试理解宏中的bind_quoted并拥有以下宏模块:

defmodule Debugger do

  defmacro log(expression) do
    if Application.get_env(:debugger, :log_level) == :debug do
      quote bind_quoted: [expression: expression] do
        IO.puts "============="
        IO.inspect expression
        IO.puts "============="
        expression
      end
     else
        expression
    end
  end
end

然后在shell中,我按照模块

进行操作
iex(1)> import_file "debugger_fixed.exs"
{:module, Debugger,
 <<70, 79, 82, 49, 0, 0, 6, 224, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 158, 131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115, 95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>,
 {:log, 1}}
iex(2)> require Debugger
nil
iex(3)> Application.put_env(:debugger, :log_level, :debug)
:ok
iex(4)> remote_api_call = fn -> IO.puts("calling remote API...") end
iex(7)> Debugger.log(remote_api_call.())

作为最后一行的结果,我有

calling remote API...
=============
:ok
=============
:ok

但我希望

=============
calling remote API...
:ok
=============
:ok

我知道bind_quoted只执行一次表达式 我的问题是,有人可以解释为什么我有意想不到的结果?

2 个答案:

答案 0 :(得分:1)

第二个:ok是您的表达式的结果,由REPL打印,而不是您的代码。 IO.puts返回:ok。 您可以签入Macro.expand - ed代码,表达式只需评估一次。

iex(9)> quote(do: Debugger.log(remote_api_call.())) |> Macro.expand(__ENV__) |>
...(9)> Macro.to_string |> IO.puts
(
  expression = remote_api_call.()
  (
    IO.puts("=============")
    IO.inspect(expression)
    IO.puts("=============")
    expression
  )
)

答案 1 :(得分:1)

只需复制粘贴我在Elixirforum – Get unexpected result with bind_quoted statement给出的答案:

这并不奇怪,也没有记录,但如果你对bind_quoted使用quote/2选项,它会扩展为类似的内容:

# your code
quote bind_quoted: [foo: foo] do
  IO.inspect foo
end
# what the compiler makes out of it in a first expansion pass of many!
quote do
  foo = unquote(foo)
  IO.inspect foo
end

所以在你的例子中注入的是:

quote do
  expression = unquote(expression)
  IO.puts "============="
  IO.inspect expression
  IO.puts "============="
  expression
end

以这种方式看待,应该明白为什么你可以得到你的输出。

即使使用bind_quoted打字的次数要少得多,我强烈反对使用它:

  1. 上面举了一个例子,你放松了对执行顺序的控制
  2. 你可能会忘记哪些项目没有引用,哪些来自“内部”
  3. 你失去了手动取消引用其他绑定的能力,这是一个全有或全无的决定。
  4. 修改

    在我忘记它之前......记录应该没有副作用(除了记录;))。因此,要么从您的“基础”函数中记录您即将进行远程API调用,要么从您现在正在进行调用的远程API函数中记录。但是不要将一个函数传递给记录器执行它的记录器......由于宏中的代码注入可能会对上下文产生有害的变化!