The Gen*
behaviours in Elixir (and erlang) provide a pure interface.
One of the benefits of this is business logic is easy to test. No need to start processes just pass the arguments you want to test to handle_call
for example and assert on one of the results.
I’m sure I read in a book that a pure interface was an important part of the setup, unfortunately I can’t remember which one so if anyone can point me to that again I’d be grateful.
Unfortunately with GenServer
implemented as it is several things can’t be done in a pure fashion. For example sending a reply to a call in response to a later message.
It’s a fairly contrived example but I don’t believe this can be implemented in a pure manner.
defmodule PairUp do
use GenServer
def handle_call({:pair, pid}, from, :none) do
{:noreply, {:waiting, pid, from}}
end
def handle_call({:pair, pid2}, from2, {:waiting, pid1, from1}) do
GenServer.reply(from1, {:other, pid2})
{:reply, {:other, pid1}, :none}
end
end
I was thinking with some small changes to the GenServer design purity could be regained.
defmodule PairUp do
use AltServer
def handle_call({:pair, pid}, from, :none) do
{[], {:waiting, pid, from}}
end
def handle_call({:pair, pid2}, from2, {:waiting, pid1, from1}) do
messages = [
{from2, {:other, pid1}},
{from1, {:other, pid2}},
]
{messages, :none}
end
end
The key changes to this interface is that :send
/:nosend
are replaced by a list of {target, message}
pairings, an empty list giving a same behaviour as no send.
I think the structure {[{target, message], state}
could be treated as a writer monad. This might even be a helpful model to add type safety to message sending.
This post is really just me musing. my questions are?
- Is this a great idea or a horrible idea
- Does something similar exist already