BenSchZA

BenSchZA

Creating a GenServer `handle_call` macro / decorator

I’ve created a custom GenServer macro, to abstract the logic from the user of a project I’m working on, Cadex. It’s used for modelling and simulating basic differential equation based models and eventually complex systems.

I’ve been experimenting with different ways I can create state update functions like below, and make it a bit cleaner - so the user only has to implement the core logic. Essentially I want to take the handle_call function and insert code before and after, adapt the function interface, but I’m struggling to get my head around it. You can see my WIP here: cadex/lib/cadex at robots-marbles-7 · BenSchZA/cadex · GitHub in decorators.ex and model.ex.

def handle_call(
        {:update, var},
        _from,
        state = %Cadex.Types.State{current: current, delta: delta}
      )
      when var == :box_A do
    increment =
      &(&1 +
          cond do
            current[var] > current[:box_B] -> -1
            current[var] < current[:box_B] -> 1
            true -> 0
          end)

    delta_ = %{var => increment}

    state_ =
      state
      |> Map.put(:delta, Map.merge(delta, delta_))

    {
      :reply,
      state_,
      state_
    }
  end

Rather something cleaner like this:

@state_update(:box_A)
def update_box_A(
        {:update, var},
        state = %Cadex.Types.State{current: current, delta: delta}
      ) do
    increment =
      &(&1 +
          cond do
            current[var] > current[:box_B] -> -1
            current[var] < current[:box_B] -> 1
            true -> 0
          end)

    {var: increment}
  end

In the GenServer macro, I’d then like to insert the rest of the code, and make sure the handle_call behaviour(?) is still implemented correctly and returns the right result. Is this possible?

I think I’ve messed around enough trying to implement a solution, and maybe some feedback would help me on my way! Appreciate any guidance :slight_smile:

Marked As Solved

lucaong

lucaong

If I understand, you want to allow the user of your library to specify how the state should be updated, without requiring them to implement the whole GenServer and without leaking private implementation details that should not affect the user. Is that correct?

If so, initially leave aside the GenServer and its state and think about a functional core for the state update logic. I do not understand the details of your example, but it seems that the user should specify how to update the :delta given some information in var and current. If that is true, you could let the user supply a module that fulfills a specified behavior, something like this:

# Behavior (adjust the types to the correct ones)
defmodule YourBehaviour do
  @callback update(var, current) :: delta
end

# User defined functional implementation
defmodule UserImplementation do
  @behaviour YourBehaviour

  @impl true
  def update(var, current) do
    # compute and return delta
  end
end

# Provide the implementation, something like:
{:ok, pid} = YourLibrary.start_link(impl: UserImplementation)

I might have gotten the details wrong, but the important thing is that you define a functional behavior so that the user defined functions receive all the needed data and no more than that, and return the updated value.

Your GenServer will then maintain the state and use the module supplier by the user to update it. This way, the user does not access implementation details or private data structures, and your GenServer logic can be tested with your own test implementation. Runtime concerns like handling concurrency or supervision are left outside of the functional core. Also, no macro is needed.

The GenServer is a runtime concern that is better separating from the actual update logic. This popular blog post elaborates more on the idea (the author, @sasajuric, hangs out in this forum).

Also Liked

al2o3cr

al2o3cr

Also check out gen_statem - it’s an example of an abstraction on top of the GenServer behavior that abstracts away state-updating and event routing.

LukeWood

LukeWood

I wrote a library to do this recently -

I didn’t like how it turned out so I never pushed forward - but you might find some value in the source code

https://github.com/LukeWood/GenClient

ityonemo

ityonemo

If you want a gen_statem that looks like genserver, I wrote StateServer — state_server v0.4.10

Where Next?

Popular in Questions Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
Kurisu
For example for a current url like http://localhost:4000/cosmetic/products?_utf8=✓&amp;query=perfume&amp;page=2, I would like to get: ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lists...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New

Other popular topics Top

Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

We're in Beta

About us Mission Statement