It seems odd that state must be passed around all the time. Can't State module hold itself, so I could do something like

state = %State{size: size}
case state.is_full? do

Here state is always passed. I suppose this is how it is in FP?

defmodule Chatter.Room do
  use GenServer  

  defmodule State do
    defstruct size: 3, players: []

    def is_full?(state) do
      Enum.count(state.players) >= state.size

    def add_player(state, player) do
      case State.is_full?(state) do
        true -> {:room_is_full, state}
        _    -> {:ok, append_player(state, player)}

    defp append_player(state, player) do
      %{state | players: state.players ++ [player]}

  def open(size) do
    {:ok, pid} = GenServer.start_link(__MODULE__, %State{size: size})

  def join(room, user) do
    GenServer.call(room, {:join, user})

  def players(room) do
    GenServer.call(room, :players)

  def init(state) do
    {:ok, state}

  def handle_call(:players, _from, state) do
    {:reply, state.players, state}

  def handle_call({:join, player}, _from, state) do
    case State.add_player(state, player) do
      {:ok, state} -> {:reply, :ok, state}
      {error, state} -> {:reply, error, state}

1 Answer 1


Short answer -- yes, data including state is immutable.

In traditional functional programming idiom data is immutable. With this in mind, GenServer stores the current state internally. When you implement the callback functions (handle_call, handle_cast, ...) the current state is passed to your callback function. You then return a new state which then replaces the existing state inside the GenServer. Depending on the data structure used for maintaining your state, only a portion of the new data may be "deeply" copied.

If I relate this directly to your question, I would not implement state as you have. The GenServer wraps the state for you. A more erlang/elixirish outline would be something like:

defmodule ModuleName do
  # api functions 
  # place calls to cast, call, here
  def is_full?(server) do
    GenServer.call(server, {:check_full})

  # callbacks
  # match ":check_full" call
  def handle_call({:check_full}, _from, state) do
    # perform your full check actions here

  # match an ":add_user" call
  def handle_call({:add_user, {user_data}}, _from, state) do
    # perform your add_user actions here

  # catch all
  def handle_call(request, _from, state) do
    # do something here, if you want, or omit as suits your style

Certainly, you can also use guards instead of matching or use one giant handle_call function with a case or cond to separate out the pieces. The method used above is clean and easy to understand.

I may have missed the point of the question --- the short answer was, yes, we pass the state around. The longer answer was the GenServer can keep track of it for you.

  • \$\begingroup\$ I would like to note that in this example and the question, the state is managed via blocking calls. Cast/handle_cast is non-blocking and is definitely useful in many cases. \$\endgroup\$
    – uDude
    Jan 5, 2016 at 23:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.