【问题标题】:Elixir macros and bind_quotedElixir 宏和 bind_quoted
【发布时间】:2016-04-25 16:03:10
【问题描述】:

我有一个宏来定义这样的模块。

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

为什么??

【问题讨论】:

    标签: macros metaprogramming elixir


    【解决方案1】:

    我认为这是因为您在 where 处取消引用(绑定)变量 name

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

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

    【讨论】:

    • 哇,这很微妙,但很有意义。回顾一下以确保我理解:当使用bind_quoted 时,name 在引用的主体 (do..end) 的外部 未加引号。但是当bind_quoted 没有被使用时,name 在引号内被明确地不加引号。
    • 引用《绝命毒师》中的海森堡:“你说得对”。
    【解决方案2】:

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

    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
    

    【讨论】:

      猜你喜欢
      • 2016-10-31
      • 2016-05-05
      • 2020-05-25
      • 1970-01-01
      • 2017-08-13
      • 2015-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多