Reputation: 763
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
Reputation: 9109
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
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