Crowdhailer
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
Trending in Discussions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #ai
- #elixirconf-us
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #elixirconf-eu
- #metaprogramming
- #hex











Most Liked- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
peerreynders
This design rule was specified in connection to user interfaces but over the years I’ve found it to be a good design rule in general. OO often pushed for more generic solutions - frequently in the name of improved reusability - without necessarily accounting for the increase in complexity or decrease in transparency that sometimes results from trying to account for another 5%(? or less) of use cases.
So while your alternate solution may seem more “generic” most use cases only ever need one or no message - not many.
Your concern can be easily addressed by composing the logic in
handle_callwith pure functions which can be tested separately instead of testinghandle_calldirectly.If you are finding that you need to quite often dispatch multiple replies simply go with something like
Can you elaborate on what this means? In connection to functions “purity” is a well defined concept. Going from your post you seem to be largely concerned with message dispatch that isn’t handled via callback return values.
My typical solution is to implement the “business logic” in an entirely separate module and have the
GenServercallbacks simply invoke those module functions. SoPairUpwould be simplyGenServerinteraction logic (andGenServerrelated helper functions) while somePairUpLogicmodule would contain the actual “pure business functions”. So testing would focus onPairUpLogicwhilePairUpcallbacks would mostly just callPairUpLogicfunctions.michalmuskala
Being idealistic, I think this could be an improvement, but on the other hand, being realistic I know there’s just no chance to change GenServer (and probably for a good reason).
Fortunately, what you propose is already implemented in
gen_statemwhich accepts a list of “actions” in return from state functions:mjadczak
So it is!
And it turns out someone has done some work on formally modelling and verifying BEAM message passing already.
Last Post!
Crowdhailer
Not yet but I would very much like too.