是否可以通过“use/2”宏(用于 DRY)扩展 Elixir 协议?

Is it possible to extend an Elixir protocol via `use/2` macro (for DRY)?

我有一个项目,我需要定义一些名称不同但定义完全相同的协议。

我试过用 use/2 来做,但没有成功:它不起作用,来自 __using__/1 的定义没有出现在生成的协议中:

defmodule Action do
  defmacro __using__(_) do
    quote do
      def run(tool)
    end
  end
end

defprotocol Actions.ShowId do
  use Action
end

defmodule Tools.SimpleRelay do
  defstruct [:id]

  defimpl Actions.ShowId do
    def run(%Tools.SimpleRelay{id: id}), do: IO.puts(inspect(id))
  end
end

在具有协议实现定义的结构上,我得到:

warning: module Actions.ShowId is not a behaviour (in module Actions.ShowId.Tools.SimpleRelay)
  iex:8: Actions.ShowId.Tools.SimpleRelay (module)

如果我尝试使用它:

iex(6)> relay = struct(Tools.SimpleRelay, id: "ALongStringId")
%Tools.SimpleRelay{id: "ALongStringId"}
iex(7)> Actions.ShowId.run(relay)
** (UndefinedFunctionError) function Actions.ShowId.run/1 is undefined or private
    Actions.ShowId.run(%Tools.SimpleRelay{id: "ALongStringId"})

这是错误还是目的?还是我的定义不正确?

我试图在 elixirforum 上问同样的问题(并在那里发布了这个任务背景),到目前为止我得到的唯一建议是这是一个错误。在开issue之前,我想澄清一下这确实是一个bug。

谢谢!

Kernel.defprotocol/2 delegates to Protocol.__protocol__/2 which in turn redefines def/2 通过取消导出 Kernel.def/2 并改为导入 Protocol.def/1。在您的宏的上下文中,def 宏指的是导入的 ATM Kernel.def/2,这会破坏所需的行为。

我不确定这是否是一个错误(尽管错误消息可能更具描述性),但您可以通过显式调用 Protocol.def/1 [=33= 轻松克服它], 使用 FQN.

defmodule Action do
  defmacro __using__(_) do
    quote do
    # ⇓⇓⇓⇓⇓⇓⇓⇓  HERE
      Protocol.def(run(tool))
    end
  end
end

defprotocol Actions.ShowId do
  use Action
end

defmodule Tools.SimpleRelay do
  defstruct [:id]

  defimpl Actions.ShowId do
    def run(%Tools.SimpleRelay{id: id}), do: IO.puts(inspect(id))
  end
end

如果您不喜欢格式化程序如何处理 Protocol.def/1 周围的括号,请随时将其委托给某些本地 defp/1 修改您的 [=22] =] 和

[
  inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
  locals_without_parens: [:defp]
]