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

时间:2017-05-04 17:09:20

标签: elixir metaprogramming

我的协议具有单一功能,并且具有相对复杂的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中的黑客更好的方法?这样做的惯用方法是什么?我宁愿不将Foo变成一种行为,因为协议的调度和合并符合使用模式。

1 个答案:

答案 0 :(得分:3)

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

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