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

时间:2019-01-31 22:53:15

标签: macros elixir metaprogramming

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


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

2 个答案:

答案 0 :(得分:1)

之前发布的答案过于复杂。我们有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]

答案 1 :(得分:0)

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

[1]出于某种原因,当我使用Enum.each/2代替下面的quote块的列表理解时,它将无法工作。

Wrapper遍历所有   提供的模块,并为   每个调用相同名称的函数   相应的模块。

例如use Wrapper, [A,B]将创建   以下功能(其中A   有lofa/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

资源

谢谢大家!