如何制作包含其他模块功能的包装器模块?

How to make a wrapper module that includes functions from other modules?

正在使用 CQRS 模式开发 Web 应用程序(通过 Commanded),并希望在单个模块中公开来自 ReadWrite 模块的函数。例如,对 Phoenix 控制器隐藏上下文的实现细节。


我知道,可以简单地将上下文模块(例如,Accounts)分成两部分。

主要归功于 @velimir for his 5 year-old-answer,但最后的资源也非常宝贵。与@velimir 解决方案的不同之处在于,传递的 Elixir 模块需要 Macro.expand/2-ed(因为它们不是简单的原子),并且它可以处理多个参数 1.

[1] 出于某种原因,当我对 quote 块使用 Enum.each/2 而不是下面的列表理解时,它不起作用。

Wrapper 遍历所有导出 提供的模块,并创建一个函数 每个,调用同名函数的 对应的模块。

例如,use Wrapper, [A,B],将创建 以下函数(其中 Alofa/0Bmiez/0):

def lofa, do: A.lofa()
def miez, do: B.miez()

Wrapper 模块:

defmodule Wrapper do

  defmacro __using__(modules) do

    user_defs =
      Enum.reduce(modules, [], fn(mod_ast, acc) ->
        exports =
          mod_ast
          |> Macro.expand(__ENV__)
          |> apply(:module_info, [:exports])

        pre_defs = [module_info: 0, module_info: 1, __info__: 1]

        [ {mod_ast, exports -- pre_defs} | acc]
      end)

    for {module, exports} <- user_defs do
      for {func_name, arity} <- exports do
        args = make_args(arity)
        quote do
          def unquote(func_name)(unquote_splicing(args)) do
            unquote(module).unquote(func_name)(unquote_splicing(args))
          end
        end
      end
    end
  end

  defp make_args(0), do: []
  defp make_args(arity) do
    Enum.map 1..arity, &(Macro.var :"arg#{&1}", __MODULE__)
  end
end

资源

谢谢大家!

之前发布的答案非常复杂。我们有 Kernel.defdelegate/2 正是为了这个目的。此外,目前还不清楚如何处理具有 同名函数 .

的模块
defmodule M1, do: def f1, do: 42
defmodule M2, do: def f2(_a1, _a2), do: 42

defmodule Wrapper do
  defmacro __using__(modules) do
    user_defs =
      modules
      |> Enum.map(&Macro.expand(&1, __ENV__))
      |> Enum.map(&{&1, &1.module_info(:exports)})

    for {module, exports} <- user_defs do
      for {func, arity} <- exports, func not in ~w|module_info __info__|a do
        args = for i <- 0..arity, i > 0,
          do: Macro.var(:"arg#{i}", __MODULE__)

        quote do
          # Use as: unquote("#{func}_#{module}") to resolve dups
          defdelegate unquote(func)(unquote_splicing(args)),
            to: unquote(module), as: unquote(func)
        end
      end
    end
  end
end

defmodule Test, do: use Wrapper, [M1,M2]