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?
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