Hello,
When using protocols, most complex code used in defimpl MyProtocol, for: MyStruct
needs to use multiple helper functions from the MyStruct
module.
So what I generally end up with is a dummy defimpl
that just forwards all calls to the implementations located in MyStruct
, so those implementations can just use any local function and the exported API of MyStruct
can change without worrying of modifying the protocol implementation accordingly.
Some might say that it is OOP to tie implementation to the data structure, but OOP is more like tying the implementation to the state. In my case, this is just the same as a regular defimpl
with one more level of indirection.
The problem is that it is tedious to write all those forwarding defimpl
so I tend to just write an implementation for Any
and write all the forwarding calls in the __deriving__
macro.
And finally yesterday night I just wrote a macro that takes a protocol name and the defs do
block, creates the protocol along with the implementation for Any
containing the forwarded calls and the actual implementation for Any
(that just raises).
This is basically an interface.
So I call this:
require Ark.Interface
Ark.Interface.definterface MyProtocol do
@spec get_stuff(t, binary) :: {:ok, binary} | {:error, :not_found}
def get_stuff(t, path)
end
And I’m done, I can just @derive
the protocol for some structs and implement the callbacks in the struct’s module.
The interface is an actual Elixir protocol that can still be implemented with defimpl
for any type (well, except for the Any
type :D).
What do you think about that ?
My main gripe with Elixir protocols is just that a defimpl
embedded in a module cannot access local functions.
You have to write:
defmodule X do
defstruct [:val]
def new(n) when is_integer(n) do
%__MODULE__{val: n}
end
def wrap(n) do
"the val is #{n}"
end
defimpl Prot do
def to_s(%{val: n}), do: X.wrap(n)
end
end
instead of this:
defmodule X do
@derive Prot
defstruct [:val]
def new(n) when is_integer(n) do
%__MODULE__{val: n}
end
defp wrap(n) do # defp
"the val is #{n}"
end
@impl Prot
def to_s(%{val: n}), do: wrap(n)
end