Search code examples
elixirerlang-otpgen-server

Pop functionality in stack server with OTP (Elixir)


In the book Programmers passport OTP by Bruce Tate there is the exercise to build a stack server similar to the example given by GenServer in their documentation but without the use of GenServer, i.e. with OTP functionality only. Implementing the pop functionality of the stack is the problem. This function (along with the push function of the stack) should be in the Core module and functions in this module are supposed to be reducers and free of side-effects. How am I able to implement pop as a call that returns the popped element to the client while having the necessary functionality in the core module and adhering to the rules of the core functions?

In the core module I first have what I wish to have:

defmodule Stack.Core do
  def pop([_head | tail]), do: tail
  ...
end

This function is a reducer and free of side-effects. However, the popped element (head) is not returned to the client. I then tried having this in the boundary layer:

defmodule Stack.Boundary do
  ...
  def run(state) do
    state
    |> listen()
    |> run()
  end

  def listen(state) do
    receive do
      {:pop, pid} ->
        [head | _tail] = state
        send(pid, {:pop, head})
        Core.pop(state)
    end
  end
end

But this has the same functionality as in the Core.pop/1, so Core.pop/1 wouldn't even be necessary anymore. How does one implement pop in the stack and make it a call handle?


Solution

  • I used “reducer” according to Bruce Tate: “The main functions in our core, those that do the bulk of the work, are reducers. These functions have a first argument of some type and return data of that same type.” ~ Programmer Passport OTP

    That is the same definition as in https://blog.plataformatec.com.br/2015/05/introducing-reducees/

    That means, we are after some generic function, accepting a list, and an actual what-to-do-with-head function and returning the reduced value back.

    In our case, that would be popping the value, calling a function on it, and returning the tail back.

    defmodule Core do
      def pop([], _fun), do: raise "empty"
      def pop([head | tail], fun) do
        fun.(head)
        tail
      end
    end
    

    and use it like

        receive do
          {:pop, pid} ->
            Core.pop(state, &send(pid, {:pop, &1}))
            # or Core.pop(state, fn h -> send(pid, {:pop, h}) end)
        end