Можно ли расширить протокол Elixir с помощью макроса `use / 2` (для DRY)? - PullRequest
0 голосов
/ 28 апреля 2020

У меня есть проект, в котором мне нужно определить несколько протоколов с разными именами, но иметь абсолютно одинаковые определения.

Я пытался сделать это с use/2, но не повезло: он не ' t, определения из __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 (и разместил там этот фон с заданиями), и единственное предложение, которое я получил до сих пор, это то, что это ошибка. Я хотел бы уточнить, что это действительно ошибка перед открытием проблемы.

Спасибо!

1 Ответ

0 голосов
/ 28 апреля 2020

Kernel.defprotocol/2 делегирует Protocol.__protocol__/2, который, в свою очередь, переопределяет def/2, не экспортируя Kernel.def/2 и импортируя вместо него Protocol.def/1. В контексте вашего макроса макрос def относится к импортированному банкомату Kernel.def/2, что нарушает требуемое поведение.

Я не уверен, является ли это ошибкой (сообщение об ошибке могло быть более информативным хотя,) но вы можете легко преодолеть это, вызвав Protocol.def/1 явно , используя 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 или что-то и , поправьте .formatter.exs на

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