While writing a GenServer implementation that keeps record of a large number of event, I realized that I am writing lots of boilerplate code and started wondering if there is a better syntax for writing functions with multiple clauses.
Here is my code (truncated for readability)
defmodule EventCounter do
use GenServer
# usage
# > {:ok, h} = EventCounter.start_link()
# > GenServer.call(h, :ev20)
# :ok
# > GenServer.call(h, :ev21)
# {:error, "invalid event"}
# > GenServer.call(h, {:get_count, :ev20})
# 1
@impl true
def handle_call({:get_count, event}, _from, state),
do: {:reply, Map.get(state, event, 0), state}
# handlers for all valid events
@impl true
def handle_call(:ev01 = valid_event, _from, state), do: {:reply, :ok, count_events(state, valid_event)}
@impl true
def handle_call(:ev02 = valid_event, _from, state), do: {:reply, :ok, count_events(state, valid_event)}
@impl true
def handle_call(:ev03 = valid_event, _from, state), do: {:reply, :ok, count_events(state, valid_event)}
# --- HERE COME HANDLERS FOR EV04 TO EV19 ---
@impl true
def handle_call(:ev20 = valid_event, _from, state), do: {:reply, :ok, count_events(state, valid_event)}
# fallback handler
@impl true
def handle_call(_illegal_event, _from, state), do: {:reply, {:error, "invalid event"}, state}
@impl true
def init(state), do: {:ok, state}
def start_link() do
GenServer.start_link(__MODULE__, %{})
end
defp count_events(state, event), do: state |> Map.update(event, 1, &(&1 + 1))
end
As you can see I am having 20 handle_call’s having the same implementation.
I was hoping that I could make it tidy by using function headers, something like this:
# handlers for all valid events
@impl true
def handle_call(valid_event, _from, state)
def handle_call(:ev01, _from, state)
def handle_call(:ev02, _from, state)
def handle_call(:ev03, _from, state)
# --- HERE COME HANDLERS FOR EV04 TO EV19 ---
def handle_call(:ev20, _from, state),
do: {:reply, :ok, count_events(state, valid_event)}
but this didn’t work.
I believe that this is a common problem. Does anyone know a way to avoid the boilerplate code while allowing only the valid events?