Implementing a web service and cli tool using the free monad
Posted by
Niklas Leopold
on September 07, 2020 ·
32 mins read
About the post
In this post we will expand on the exercise 6.11 in Functional programming in Scala. The exercise invites the reader to use the State monad to implement a finite state automaton. The automaton simulates a candy machine.
Here we will use the Free monad instead. We will also try to use our program in different contexts, first in a CLI-tool and then in a web service.
In the post I will skip some of the boilerplate code but the fully working example (including boilerplate and some tests) are available at github.
What is a Free Monad?
This post assumes that you already have some basic familiarity with the Free monad, if not, read all about it here.
The rules for the Candy Machine
Here are the rules for the candy machine:
Inserting a coin into a locked machine will cause it to unlock if there’s any candy left.
Turning the knob on an unlocked machine will cause it to dispense candy and become locked.
Turning the knob on a locked machine or inserting a coin into a unlocked machine does nothing.
A machine that’s out of candy ignores all inputs.
A CLI version of the candy machine
Ok, so here we go, let’s try to implement a version of the candy machine that provides a CLI interface for the user.
The output from the program should look something like this:
MachineState
We need something that represent the current state of the candy machine:
When we do the web service implementation we need an id so that it’s possible to create several candy machines, in the cli version we will always set the id to 0.
Defining the Algebras
We need two algebras, one for the IO:
and one for the state changes:
As you can see the algebras takes into account that things can go wrong and handles this by using the Either monad.
The IO algebra gives our program the ability to read/write things from some external source, in the context of the CLI program this will be from the prompt.
The MachineOp algebra on the other hand gives the ability to get the current state, update the state and finally set the initial state for the candy machine.
Lifting the algebras
Now we have to lift the algebras into the Free monad, first the IO:
and then the MachineOp:
If you are curious about that inject thingy I suggest you read this blog post since it awesome.
The CLI Candy Machine
Ok, so now we almost are ready to write our Cli candy program, but first let’s define some helper types so that the function signatures becomes a little less verbose:
I leave out the actual type of the CandyMachine so that the implementor (i.e. me, myself and I) have a little more freedom when later assembling our application, more about that later.
I have divided the CLI program in two parts, one that handles IO from the user and one that takes care of maintaining the rules for the machine and the state.
We start by looking at the IO-part:
The essense of the program is to get input from the user, translate it into a Request and then provide the request to an eventHandler that handles the request and then finally give feedback to the user about the outcome.
Error handling is also included since it could be that the user gives the wrong input or breaks one of the rules of the candy machine.
Let’s move on:
This part of the program is all about making sure that we are following the rules of the machine and move our machine from one state to another. Please observe that we are still in the pure functional world at this stage, the actual state handling will be done in the interpreter of the algebra.
Let’s assemble our programs to a complete CLI-program:
Here I commit myself to a concrete type for the CandyMachine: EitherK[MachineOp, IOA, A], EitherK gives us the possibility to mix our two algebras.
I also injects the requestHandler into the CliCandyProgram.
Let’s move away from our pure functional world and travel to the realm of side effects.
The compiler for IO is pretty simple, Read tries to take input from the user and then wrap it in a Future. The same goes for Write but here we try to give feedback to the user instead.
Please note that we also commit ourselves to that the Free monad should be mapped to ProgramResult (just an alias for Future).
Here is the compiler for the RequestHandlerProgram:
The interpreter forwards the request to an Actor that maintain the state of the application. The behave function of the actor looks like this:
The state is updated by changing the behaviour of the actor. Since the Actor is single threaded (illusion) this is all very thread safe. For the full implementation of the Actor I refer to github.
Running the program
Time to run the program:
Once again I have left out some code (mostly concerning the setup of the actor system), but this is the essence: assemble our interpreter and then use it to run our program.
A REST API for the Candy Machine
Let’s try to use our program in a different context, let’s implement a REST API. We will use Akka Http to create the API. I plan to reuse the RequestHandlerProgram and the ActorMachineInterpreter that we developed for our CLI program.
We don’t need the IO-part of our program since this will be handled by Akka Http. Here’s the route for our Rest service:
We use our pure program and the interpreter to handle the requests as they arrive from the client.
The API gives the client the possibility to create a new machine, get the status of the machine and to insert a coin or try to get a candy.
We create the interpreter like this:
Once again I have left out things that is not that important for this post (mostly to setup the actor system and the startup of the web service), check github for the full implementation.
Conclusion
We have tried and succeeded to implement a CLI program for a Candy Machine, we later reused part of the program to provide a REST Api.