如何动态创建具有功能的模块

How to dynamically create modules with functions

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

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

现在我的问题是:如何使用动态创建的函数名称动态生成模块,例如。 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

注意我用

实现了我想要的
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

但它似乎很老套。

我相信这是由于嵌套的宏调用而发生的(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