Re-deriving gen_server

The “server” part

The Simplest Thing That Works

The interface

S = kvstore:start().
kvstore:set(name, "Colin", S).
V = kvstore:fetch(name, S).

The client functions

start() ->
    State = dict:new(),
    Handler = fun() -> loop(State) end,
    spawn(Handler).
    
set(Key, Value, Pid) ->
    Pid ! {set, Key, Value}.
    
fetch(Key, Pid) ->
    Pid ! {self(), {fetch, Key}},
    receive Value -> Value end.

The server process loop

loop(State) ->
    receive
        {From, {fetch, Key}} ->
            {ok, Value} = dict:find(Key, State),
            From ! Value,
            loop(State);
        {set, Key, Value} ->
            NewState = dict:store(Key, Value, State),
            loop(NewState)
    end.

Re-plumbing the server

Split up one-way and two-way message handling

loop(State) ->
    receive
        {From, Message} ->
            {NewState, Value} = handle_call(Message, State),
            From ! Value;
        Message ->
            NewState = handle_cast(Message, State),
    end,
    loop(NewState).

handle_call({fetch, Key}, State) ->
    {ok, Value} = dict:find(Key, State),
    {State, Value}.

handle_cast({set, Key, Value}, State) ->
    dict:store(Key, Value, State).

Key points

Add increment function

increment(Key, Pid) ->
    Pid ! {self(), {increment, Key}},
    receive Value -> Value end.

… and another handle_call/2 clause (no change to loop/1)

handle_call({increment, Key}, State) ->
    NewState = dict:update_counter(Key, 1, State),
    {ok, Value} = dict:find(Key, NewState),
    {NewState, Value};

New interaction looks like

S = kvstore:start().
kvstore:set(age, 45, S).
V = kvstore:increment(age, S).

Re-plumbing the client

Split up one-way and two-way message sending

cast(Message, Pid) ->
    Pid ! Message.

call(Message, Pid) ->
    Pid ! {self(), Message},
    receive Value -> Value end.

Re-worked client functions

set(Key, Value, Pid) ->
    cast({set, Key, Value}, Pid).

fetch(Key, Pid) ->
    call({fetch, Key}, Pid).

increment(Key, Pid) ->
    call({increment, Key}, Pid).

Re-work start/0

start() ->
    State = init(),
    Handler = fun() -> loop(State) end,
    spawn(Handler).

init() -> dict:new().

Taking Stock

In a table

API Generic Plugin
start/0 init/1
set/3 cast/2 handle_cast/2
fetch/2 call/2 handle_call/2
increment/2
loop/1

What Else?