在Elixir中使用代理实现协议

时间:2016-10-14 02:37:20

标签: elixir

我想在Elixir中定义一个协议,然后有几个模块实现它。我的问题是那些模块只是Agent包装器,所以:

** (Protocol.UndefinedError) protocol Proto not implemented for #PID<0.88.0>
    test.exs:1: Proto.impl_for!/1
    test.exs:2: Proto.foo/1
    (elixir) lib/code.ex:363: Code.require_file/2

然后导致,

{{1}}

有没有办法直接处理?

2 个答案:

答案 0 :(得分:2)

要使用此类协议,您必须制作AB结构,并从相应的start_link函数返回它们。您还需要Agent周围的包装器才能调用Agent个函数。我为此创建了一个单独的模块,因为如果你创建更多具有相同结构的结构,可以重复使用它:

defprotocol Proto do
  def foo(proto)
end

defmodule A do
  defstruct [:pid]

  def start_link() do
    WrappedAgent.start_link(A, fn -> :a end)
  end
end

defimpl Proto, for: A do
  def foo(proto) do
    WrappedAgent.get(proto, fn a -> {:a, a} end)
  end
end

defmodule B do
  defstruct [:pid]

  def start_link() do
    WrappedAgent.start_link(B, fn -> :b end)
  end
end

defimpl Proto, for: B do
  def foo(proto) do
    WrappedAgent.get(proto, fn b -> {:b, b} end)
  end
end

defmodule WrappedAgent do
  def start_link(module, f) do
    with {:ok, pid} <- Agent.start_link(f),
      do: {:ok, %{__struct__: module, pid: pid}}
  end

  def get(%{pid: pid}, f), do: Agent.get(pid, f)
end

{:ok, a} = A.start_link()
IO.inspect Proto.foo(a)

{:ok, b} = B.start_link()
IO.inspect Proto.foo(b)

输出:

{:a, :a}
{:b, :b}

答案 1 :(得分:2)

为了澄清一下,为数据类型实现了协议。您可以为所有标准数据类型和您在模块中定义的任何结构实现协议。

[Atom, Integer, Float, BitString, Regexp, PID, Function, Reference, Port, Tuple, List, Map]

想想“相同的功能,不同的数据类型”。与行为形成鲜明对比,这与您最初的想法更为接近。在那里,您定义了一组函数,这些函数具有相同的arity(并且按照惯例,相同的输入),您在模块中实现这些函数以允许其他模块 以标准方式使用您的模块。