如何使用函数动态创建模块

时间:2017-06-19 06:51:01

标签: macros elixir metaprogramming

在编译阶段,我可以轻松地生成函数:

defmodule A1 do
  defmodule A2 do
    Enum.each %{m: 42}, fn {k, v} ->
      def unquote(k)(), do: unquote(v)
    end 
  end 
end
IO.puts A1.A2.m
#⇒ 42

另外,我可以在函数调用中生成带有函数的模块:

defmodule B1 do
  def b2! do
    defmodule B2 do
      # enum is for the sake of future example
      Enum.each %{m1: 42}, fn {_k, v} ->
        # def b2(), do: unquote(v) WON’T WORK (WHY?), BUT
        @v v
        def b2(), do: @v
      end 
    end 
  end 
end
B1.b2! # produce a nested module
IO.puts B1.B2.b2 # call a method
#⇒ 42

现在我的问题是:如何使用动态创建的函数名称动态生成模块 ,e。 G:

defmodule B1 do
  def b2! do
    defmodule B2 do
      Enum.each %{m1: 42, m2: 3.14}, fn {k, v} ->
        @k k
        @v v
        def unquote(@k)(), do: @v # THIS DOESN’T WORK
      end 
    end 
  end 
end

NB 我能够实现我想要的目标

defmodule B1 do
  def b2! do
    defmodule B2 do
      Enum.each %{m1: 42, m2: 3.14}, fn {k, v} ->
        ast = quote do: def unquote(k)(), do: unquote(v)
        Code.eval_quoted(ast, [k: k, v: v], __ENV__)
      end
    end 
  end 
end

但似乎非常黑客。

1 个答案:

答案 0 :(得分:2)

我认为这是由于嵌套的宏调用(defdefmodule都是宏)而发生的。如果您在其中放置unquote,则会从顶级def中取消引用:

defmodule B1 do
  k = :foo
  v = :bar
  def b2! do
    defmodule B2 do
      def unquote(k)(), do: unquote(v)
    end
  end
end

B1.b2!
IO.inspect B1.B2.foo

打印

:bar

Module.create/3建议使用该函数在正文为AST时动态创建模块。有了它,代码变得比使用Code.eval_quoted/3的hacky解决方案更优雅:

defmodule B1 do
  def b2! do
    ast = for {k, v} <- %{m1: 42, m2: 3.14} do
      quote do
        def unquote(k)(), do: unquote(v)
      end
    end 
    Module.create(B1.B2, ast, Macro.Env.location(__ENV__))
  end 
end

B1.b2!
IO.inspect B1.B2.m1
IO.inspect B1.B2.m2

输出:

42
3.14