在 Elixir 的宏中实现具有行为的协议

Implement a protocol with a behaviour in a macro in Elixir

我有一个功能单一但相对复杂的协议API:

defprotocol Foo do
  def complex(foo, x, y)
end

我想提供一种方法来为常见且更简单的用例实施此协议。经过一些实验,我得出了以下结论:

defmodule Bar do
  @callback simple(any, any) :: boolean

  defmacro __using__(_) do
    quote do
      @behaviour Bar

      defimpl Foo do
        # TODO this is really hacky. What is a better way to reference parent module?
        # Note that the 1 in drop(1) is from Foo's module name
        @parent_module __MODULE__ |> Module.split |> Enum.drop(1) |> Module.concat
        def complex(bar, x, _y) do
          matches = @parent_module.simple(bar, x)
          if matches, do: [x], else: []
        end
      end
    end
  end
end

现在我可以通过 use Bar 实现 Foo,但是 @parent_module 的确定和使用方式似乎是错误的。有没有比使用上面 TODO 中的 hack 更好的方法?这样做的惯用方法是什么?我宁愿不把 Foo 变成一种行为,因为协议的调度和整合符合使用模式。

您可以获取父模块的名称并将其存储在局部变量中,然后将其放入实现中的属性中,然后使用它:

defmodule Bar do
  @callback simple(any, any) :: boolean

  defmacro __using__(_) do
    quote do
      @behaviour Bar

      parent_module = __MODULE__

      defimpl Foo do
        @parent_module parent_module

        def complex(bar, x, _y) do
          matches = @parent_module.simple(bar, x)
          if matches, do: [x], else: []
        end
      end
    end
  end
end