Search code examples
dictionaryelixirimmutabilitygen-servermutability

Storing state in Elixir


Recently I tackled a problem which involved updating a large number of key values.

Naturally, I considered using a Map, with operations like Map.put/3.

However this seemed insufficient, given the immutable nature of data structures in Elixir:

iex> m = Map.put(%{}, :a, 1)
%{a: 1}
iex> Map.put(m, :b, 2)
%{a: 1, b: 2}
iex> m
%{a: 1}

I then solved the problem by holding the state of the Map in a GenServer, and updating it using handle_cast/3 calls.

Generally, is this the right approach, or was this too much here?


Solution

  • I then solved the problem by holding the state of the Map in a GenServer [...] Generally, is this the right approach, or was this too much here?

    It heavily depends on your goal. There are many different ways to store the state. Rebinding variables like:

    m = Map.put(%{}, :a, 1)
    #⇒ %{a: 1}
    m = Map.put(m, :b, 2)
    #⇒ %{a: 1, b: 2}
    

    Does not store anything. It binds the local variable m to RHO and as soon as the control flow leaves the scope, this variable becomes garbage collected. Whether you need the aforementioned map within a single scope, GenServer (and other state holders) is an overkill.


    OTOH, if you need to store the state for a long time and share it between different scopes (e. g. between different processes,) GenServer is the simplest way to accomplish that. In Elixir we have Agent module to decrease the boilerplate for GenServer that is used as a simple in-memory storage, but my advice would be to always use GenServer: sooner or later Agent will become too tight for your purposes.

    Also, one might use ets module to keep in-memory key-value storage, shared between processes.

    dets is a way to store the state between process restarts.

    And, finally, mnesia is an OTP native approach to share the state between both restarts and different nodes (in distributed environment.)