宏bind_quoted在第一次迭代时中断

时间:2019-03-25 18:09:18

标签: elixir

也许我误解了bind_quoted的用法,但是采用了这个简单的while循环宏:

defmodule Loop do
  defmacro while(expression, do: block) do
    quote bind_quoted: [expression: expression, block: block] do
      for _ <- Stream.cycle([:ok]) do
        if(expression) do
          block
        end
      end
    end
  end
end

用法:

Interactive Elixir (1.8.0) - press Ctrl+C to exit (type h() ENTER for help) 
iex(1)> c "loop.ex" 
[Loop] 
iex(2)> import Loop
iex(3)> while 1 == 1 do 
...(3)> IO.puts "hi"
...(3)> end
hi

我希望有一个无限的“ hi”循环,但只会得到一个迭代。如果我删除每个参数的bind_quoted和简单的unquote(),它将按预期工作。有任何想法吗?

谢谢

1 个答案:

答案 0 :(得分:0)

当您使用bind_quoted时,它将评估您传入的片段。因此,IO.puts "hi"是在[expression: expression, block: block]中而不是在您的if块中进行评估的。您只是在此处注入:ok

作为一个例子,让我们定义一些除了评估传入内容外什么都不做的宏:

defmodule AB do
  defmacro a(block) do
    quote bind_quoted: [block: block] do
      block
      block
      :ok
    end
  end

  defmacro b(block) do
    quote do
      unquote(block)
      unquote(block)
      :ok
    end
  end
end

忽略第一个警告,让我们使用包含IO.inspect/1的块调用每个警告,然后看看会发生什么:

iex> require AB
iex> AB.a(IO.inspect(1 + 1))
... (some warnings)
2
:ok
iex> AB.b(IO.inspect(1 + 1))
2
2
:ok

在宏a/1中,sum + inspect发生在引号之外,因此仅被调用一次。在宏b/1中,总和检查发生在我们取消引用block的任何地方。