如何制作包含其他模块功能的包装器模块?
How to make a wrapper module that includes functions from other modules?
正在使用 CQRS 模式开发 Web 应用程序(通过 Commanded),并希望在单个模块中公开来自 Read
和 Write
模块的函数。例如,对 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]
,将创建
以下函数(其中 A
有 lofa/0
和 B
有 miez/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
资源
- https://elixirforum.com/t/pass-module-to-a-macro/978
- https://elixirforum.com/t/collect-modules-that-use-some-other-module-at-compile-time/13742
- https://elixirforum.com/t/how-to-call-a-function-at-compile-time/619
- https://elixirforum.com/t/what-is-the-return-value-of-quote-block/328/6
- 萨沙·尤里奇的 "Understanding Macros" series
- 克里斯·麦考德 Metaprogramming Elixir
- https://andrealeopardi.com/posts/compile-time-work-with-elixir-macros/
- Macro expansion in elixir: how to define 2 macros with one using the other?
- https://dockyard.com/blog/2017/12/07/macro-madness-how-to-use-use-well
谢谢大家!
之前发布的答案非常复杂。我们有 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]
正在使用 CQRS 模式开发 Web 应用程序(通过 Commanded),并希望在单个模块中公开来自 Read
和 Write
模块的函数。例如,对 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]
,将创建
以下函数(其中 A
有 lofa/0
和 B
有 miez/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
资源
- https://elixirforum.com/t/pass-module-to-a-macro/978
- https://elixirforum.com/t/collect-modules-that-use-some-other-module-at-compile-time/13742
- https://elixirforum.com/t/how-to-call-a-function-at-compile-time/619
- https://elixirforum.com/t/what-is-the-return-value-of-quote-block/328/6
- 萨沙·尤里奇的 "Understanding Macros" series
- 克里斯·麦考德 Metaprogramming Elixir
- https://andrealeopardi.com/posts/compile-time-work-with-elixir-macros/
- Macro expansion in elixir: how to define 2 macros with one using the other?
- https://dockyard.com/blog/2017/12/07/macro-madness-how-to-use-use-well
谢谢大家!
之前发布的答案非常复杂。我们有 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]