Elixir 宏和 bind_quoted

Elixir macros and bind_quoted

我有一个定义模块的宏。

defmodule Bar do
  def bar do
    IO.puts "I am #{inspect __MODULE__}"
  end
end

defmodule MacroFun do

  defmacro define_module(name) do
    quote do
      defmodule unquote(name) do
        import Bar
        def foo do
          bar
          IO.puts "I am #{inspect __MODULE__}"
        end
      end
    end
  end

end

defmodule Runner do
  require MacroFun

  def run do
    MacroFun.define_module Foo
    Foo.foo
  end

end

Runner.run

运行 的输出是:

I am Bar
I am Runner.Foo

这是有道理的; MacroFun.define_moduleRunner.run 中被调用,因此模块被定义并因此嵌套在 Runner 模块下。

但现在如果我将 MacroFun.define_module 更改为使用 :bind_quoted 选项:

  defmacro define_module(name) do
    quote bind_quoted: [name: name] do
      defmodule name do
        import Bar
        def foo do
          bar
          IO.puts "I am #{inspect __MODULE__}"
        end
      end
    end
  end

输出现在变成:

I am Bar
I am Foo

为什么?

我认为那是因为 你取消引用(绑定)变量 name.

的地方

在第一种情况下,您在创建模块时取消引用变量 name,因此此时绑定变量需要检查上下文(检查代码是否在另一个模块中,例如).因此,您将获得当前原子加上适当的上下文:Runner.Foo.

在第二种情况下,您在将变量 name 放入上下文之前取消引用它,因此它的值不会改变,它将是原子 Foo(无上下文)。

使用此代码,您将看到用于创建模块的正确值:

require Logger

defmodule Bar do
  def bar do
    IO.puts "I am #{inspect __MODULE__}"
  end
end

defmodule MacroFun do

  defmacro define_module(name) do
    quote do
      Logger.debug("#{inspect unquote(name)}")
      defmodule unquote(name) do
        import Bar
        Logger.debug("#{inspect unquote(name)}")
        def foo do
          bar
          IO.puts "I am #{inspect __MODULE__}"
        end
      end
    end
  end

  defmacro define_module2(name) do
    quote bind_quoted: [name: name] do
      defmodule name do
        import Bar
        Logger.debug("#{inspect name}")
        def foo do
          bar
          IO.puts "I am #{inspect __MODULE__}"
        end
      end
    end
  end
end

defmodule Runner do
  require MacroFun

  def run do
    MacroFun.define_module Foo
    Foo.foo
  end
  def run2 do
    MacroFun.define_module2 Foo2
    Foo2.foo
  end

end

Runner.run
Runner.run2

输出:

[warn]  Foo
[warn]  Runner.Foo
I am Bar
I am Runner.Foo

[warn]  Foo2
I am Bar
I am Foo2