Return type state different from expectation

I am working through “Functional Web Development with Elixir, OTP, and Phoenix” and came across a snippet that throws an exception. I might have made a typo somewhere or this might be due to a newer version of Erlang or Elixir since the book came out (didn’t have a chance to test this yet).

Page 78 indicates the state_data for the game object (GenServer) should look like this;

%{player1:.....etc....., state: :initialized}}

What I am getting is the following (using :sys.get_state);

[player1:......etc....., state: :initialized}]

My game module’s init function looks like this (seemingly identical to the book instruction)

  def init(name) do
    player1 = %{name: name, board: Board.new(), guesses: Guesses.new()}
    player2 = %{name: nil, board: Board.new(), guesses: Guesses.new()}
    {:ok, player1: player1, player2: player2, rules: %Rules{}}
  end

So I expected a Map but I got a List. Although I immediately noticed this I continued the next paragraph that tries to run handle_call when I invoke an ‘Game.add_player’ call (which tries to read attributes from the state_data) which obviously fails as it is a List not a Map;

iex(5)> Game.add_player(game, "Jeff")

20:34:28.960 [error] GenServer #PID<0.189.0> terminating
** (ArgumentError) argument error
    :erlang.apply([player1: %{board: %{}, guesses: %IslandsEngine.Guesses{hits: #MapSet<[]>, misses: #MapSet<[]>}, name: "Frank"}, player2: %{board: %{}, guesses: %IslandsEngine.Guesses{hits: #MapSet<[]>, misses: #MapSet<[]>}, name: nil}, rules: %IslandsEngine.Rules{player1: :islands_not_set, player2: :islands_not_set, state: :initialized}], :rules, [])
    (islands_engine) lib/game.ex:28: IslandsEngine.Game.handle_call/3
    (stdlib) gen_server.erl:661: :gen_server.try_handle_call/4
    (stdlib) gen_server.erl:690: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message (from #PID<0.182.0>): {:add_player, "Jeff"}
State: [player1: %{board: %{}, guesses: %IslandsEngine.Guesses{hits: #MapSet<[]>, misses: #MapSet<[]>}, name: "Frank"}, player2: %{board: %{}, guesses: %IslandsEngine.Guesses{hits: #MapSet<[]>, misses: #MapSet<[]>}, name: nil}, rules: %IslandsEngine.Rules{player1: :islands_not_set, player2: :islands_not_set, state: :initialized}]
Client #PID<0.182.0> is alive

    (stdlib) gen.erl:169: :gen.do_call/4
    (elixir) lib/gen_server.ex:921: GenServer.call/3
    (stdlib) erl_eval.erl:677: :erl_eval.do_apply/6
    (elixir) src/elixir.erl:265: :elixir.eval_forms/4
    (iex) lib/iex/evaluator.ex:249: IEx.Evaluator.handle_eval/5
    (iex) lib/iex/evaluator.ex:229: IEx.Evaluator.do_eval/3
    (iex) lib/iex/evaluator.ex:207: IEx.Evaluator.eval/3
    (iex) lib/iex/evaluator.ex:94: IEx.Evaluator.loop/1
** (EXIT from #PID<0.182.0>) shell process exited with reason: an exception was raised:
    ** (ArgumentError) argument error
        :erlang.apply([player1: %{board: %{}, guesses: %IslandsEngine.Guesses{hits: #MapSet<[]>, misses: #MapSet<[]>}, name: "Frank"}, player2: %{board: %{}, guesses: %IslandsEngine.Guesses{hits: #MapSet<[]>, misses: #MapSet<[]>}, name: nil}, rules: %IslandsEngine.Rules{player1: :islands_not_set, player2: :islands_not_set, state: :initialized}], :rules, [])
        (islands_engine) lib/game.ex:28: IslandsEngine.Game.handle_call/3
        (stdlib) gen_server.erl:661: :gen_server.try_handle_call/4
        (stdlib) gen_server.erl:690: :gen_server.handle_msg/6
        (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

Game.#28 seems to cause the problem, which looks like this;

def handle_call({:add_player, name}, _from, state_data) do
    with {:ok, rules} <- Rules.check(state_data.rules, :add_player)  # <- I don't think I am allowed to call rules on the state_data like this (although this is straight from the book)
    do
      state_data
      |> update_player2_name(name)
      |> update_rules(rules)
      |> reply_success(:ok)
    else
      :error -> {:reply, :error, state_data}
    end

Any takes?

Which version of the book do You have? In mine, which is p1_0 I have on page 78…

def init(name) do
  player1 = %{name: name, board: Board.new(), guesses: Guesses.new()} 
  player2 = %{name: nil, board: Board.new(), guesses: Guesses.new()} 
  {:ok, %{player1: player1, player2: player2, rules: %Rules{}}}
end

It returns a map as expected.

BTW this is a Keyword List, which is a List of 2 elements tuples.

player1: player1, player2: player2, rules: %Rules{}

#is equal to

[{:player1, player1}, {:player2, player2}, {:rules, %Rules{}}]

Thanks for your reply! I’ll admit, this might be a typo after all, didn’t have a chance to check the book but I think my init function might be missing the Map accolades.