如果我们检查在official docs中使用Ecto片段的示例,它说,要使用另一个模块中的宏,我们需要首先导入该模块。因此,如果我们的模块带有一个coalesce/2
宏:
defmodule CustomFunctions do
defmacro coalesce(left, right) do
quote do
fragment("coalesce(?, ?)", unquote(left), unquote(right))
end
end
end
我们需要将其导入另一个模块以使用它:
import CustomFunctions
然后,我们可以编写如下查询:
where(Post, [p], p.id in coalesce(3,5)
只要没有名称冲突,它就很好用。但是,可以说,由于某种原因,我正在创建一些模块,这些模块将仅导出一个名为query
的宏,并且很明显,要等到名称冲突开始后再花很长时间。因此,我想知道是否可以像上面这样使用宏的全名来使用宏:
require CustomFunctions
where(Post, [p], p.id in CustomFunctions.coalesce(3,5)
很显然,我尝试了一下,但是失败了,并说CustomFunctions.coalesce(3,5)
是不合适的查询元素。
那么,有没有简单的方法可以实现类似的目的?
答案 0 :(得分:2)
我不知道此功能是开箱即用的,但是您可以执行以下操作:
defmodule(Macro1, do: defmacro(coalesce(foo), do: quote(do: unquote(foo) + 1)))
defmodule(Macro2, do: defmacro(coalesce(foo), do: quote(do: unquote(foo) - 1)))
那些是带有宏名称冲突的模块。我们将它们导入为macro1_coalesce
和macro2_coalesce
。
defmodule EctoExtention do
defmacro __using__(opts) do
Enum.flat_map(opts, fn what ->
{mod, funs} =
case what do
{mod, :*} -> {mod, Module.concat([mod]).__info__(:macros)}
{mod, funs} -> {mod, funs}
end
prefix =
mod
|> to_string()
|> String.downcase()
mod = Module.concat([mod]) # or :"Elixir.#{mod}"
[
quote(do: require(unquote(mod)))
| Enum.map(funs, fn {fun, arity} ->
args = for i <- 0..arity, i > 0, do: Macro.var(:"arg_#{i}", nil)
quote do
@doc ~s"""
Macro #{unquote(fun)} imported from module #{unquote(mod)}
"""
defmacro unquote(:"#{prefix}_#{fun}")(unquote_splicing(args)),
do: unquote(mod).unquote(fun)(unquote_splicing(args))
end
end)
]
end)
end
end
现在,让我们定义从不同模块导入宏的规则。
defmodule AllMyMacros do
use EctoExtention, Macro1: [coalesce: 1], Macro2: :*
end
:*
表示导入所有宏。让我们对其进行测试:
defmodule Test do
import AllMyMacros
def info, do: Using.__info__(:macros)
def test, do: {macro1_coalesce(42), macro2_coalesce(42)}
end
IO.inspect(Test.info(), label: "macros")
#⇒ macros: [macro1_coalesce: 1, macro2_coalesce: 1]
IO.inspect(Test.test(), label: "test")
#⇒ test: {43, 41}