【问题标题】:Get unexpected result with bind_quoted statement使用 bind_quoted 语句获得意外结果
【发布时间】:2016-04-12 16:42:00
【问题描述】:

我正在尝试在宏中理解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 只执行一次表达式。
我的问题是,有人可以解释为什么我得到了意想不到的结果吗?

【问题讨论】:

  • 请不要跨多个媒体发帖,如果确实需要,请选择一个中心位置,让其他帖子链接到它!
  • @zero_coding 我是否可以建议,如果您正在尝试学习如何使用宏,那么投资 Chris McCord 的“Metaprogramming Elixir”会是明智之举吗?似乎这可能会帮助您解决这些问题。当然,这只是一个建议。

标签: elixir


【解决方案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. 您失去了手动取消引用其他绑定的能力,这是一个全有或全无的决定。

编辑

在我忘记它之前......日志记录应该没有副作用(日志记录除外;))。因此,要么从您的“基本”函数中记录您将很快进行远程 API 调用,或者从您的远程 API 函数中记录您现在正在执行调用。但是不要将函数传递给记录器,记录器确实会执行它……由于宏中的代码注入,可能会对上下文造成有害的变化!

【讨论】:

  • 首先,非常感谢您的回答。我忘记了 IO.puts return :ok 并且它在宏调用之前执行。我太傻了。
  • 它不会在宏调用之前执行,它会在每个unquote 上执行,并且使用bind_quoted 你早unquoteing。
【解决方案2】:

第二个: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
  )
)

【讨论】:

  • 这不是他想知道的第二个:ok。事实上,输出的前两行与他的预期相反。但我也花了diff-run 才意识到;)
  • 哦。是的,我的问题完全错了。不确定,我应该删除这个答案吗?..
  • 事实上,您的回答确实表明,我只是以简化的方式呈现的事实是,在给定 :do 块的实际内容之前,给宏的参数没有被引用。也许你可以改写你的前导句,让它成为问题的有效答案?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-02
  • 1970-01-01
  • 1970-01-01
  • 2012-04-09
  • 2016-02-20
  • 2022-11-25
相关资源
最近更新 更多