【问题标题】:Ecto fragment macro from a separate module without import来自单独模块的 Ecto 片段宏,无需导入
【发布时间】:2019-03-19 16:03:49
【问题描述】:

如果我们检查 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) 不是一个合适的查询元素。

那么,有没有一种简单的方法来实现这样的目标?

【问题讨论】:

    标签: elixir ecto


    【解决方案1】:

    我不知道此功能是开箱即用的,但您可能会执行以下操作:

    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_coalescemacro2_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}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-03-18
      • 1970-01-01
      • 2021-08-03
      • 2019-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多