Stephen H. Gerstacker
Stephen H. Gerstacker

Reputation: 763

Implementing A Protocol With An Agent In Elixir

I wanted to define a protocol in Elixir and then have a couple of modules implement it. My issue is that those modules are just Agent wrappers, so:

defprotocol Proto do
  def foo(proto)
end

defmodule A do
  def start_link() do
    Agent.start_link(fn -> :a end)
  end
end

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

defmodule B do
  def start_link() do
    Agent.start_link(fn -> :b end)
  end

end

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

{:ok, a_pid} = A.start_link()
value = Proto.foo(a_pid)

Which then results in,

** (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

Is there a way to handle this directly?

Upvotes: 0

Views: 134

Answers (2)

Just to clarify a bit, Protocols are implemented for Data types. You can implement a protocol for all the standard data types and any structs you define in modules.

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

Think "same function, different data types". Contrast that with behaviours, which is closer to your original idea. There you define a set of functions with the same arity ( and by convention the same inputs) that you implement in your module to allow other modules to use your module in a standard way.

Upvotes: 2

Dogbert
Dogbert

Reputation: 222128

In order to use protocols like this, you'll have to make A and B structs and return them from the respective start_link functions. You'll also need a wrapper around Agent to be able to call Agent functions. I've created a separate module for this since the same can be reused if you create more structs with the same structure:

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)

Output:

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

Upvotes: 2

Related Questions