Let me tell a little secret of mine, I’m sort of addicted to tests. Or well, at least when writing code that will be used in production. Code that is more of an experiment I’m more relaxed with, which is evident if you care to look at my github account.
What has that to do with the Free Monad you may wonder?
In this post we will write a very simple program that takes some input and then echo’s it back to the user. We will use the Free Monad to separate the core program from the messy IO-stuff. We will also write tests to prove that our program works which I guess is the main focus in this post.
This is the explanation in the cats documentation:
A free monad is a construction which allows you to build a monad from any Functor. Like other monads, it is a pure way to represent and manipulate computations.
In particular, free monads provide a practical way to:
- represent stateful computations as data, and run them
- run recursive computations in a stack-safe way
- build an embedded DSL (domain-specific language)
- retarget a computation to another interpreter using natural transformations
The output when running the program should look something like this:
As we can see the program is mostly about reading stuff from the prompt and then writing it back.
So how should the algebra look like for our Echo program? Well, we know that it must support reading and writing to the prompt so I defined it like this:
The algebra gives the program the ability to read
things from some kind of source and also the ability to write something to somewhere using print
or printLn
(print and newline).
To keep it really simple the algebra does not support error handling, but I have an example here where that is included.
Now that we have defined our algebra we can create a type for the free monad. I also create some constructors that wraps (lifts) our algebra into the context of the free monad.
And now we can express our program through the algebra:
In the code above we rely on for comprehensions to give us nice syntax for the program. This is possible since we have lifted our algebra into the Free monad, very nice.
If we run the program like this:
Nothing is shown, it looks like the program doesn’t do anything, how very disappointing.
But it’s also understandable considering that the program is an instruction of what we want to accomplish. To actually run the program we need something that interpret it!
By the way if your interested in the inner works of the Free monad, pick up the book: Functional programming in Scala, it has a nice section about it.
Let’s write a compiler so that we can actually use our program for something.
The compiler translates our algebra to concrete IO instructions, for example Println(s) -> System.out.println
.
We also selects a concrete monad Id
to represents our free monad in the “real world”. The type of the Id
monad is type Id[A] = A
so I think of it as the equivalent of the id function def identity[A](a: A): A = a
but for types.
We could of course have selected another monad if we wanted to (like Future, Try etc) which is a really cool feature. Later in the post we will in fact do so when writing tests for our program.
Let’s apply the compiler to the program.
The output is as follows:
Oh yeah, now I seem to recall that I promised tests for our Free monad. Ok, let’s do it.
When testing we don’t want to use our previous compiler since it maps our algebra to concrete IO operations. That’s good when we want to let the user do something with the program, but not so much if we want to test the program.
Here’s a compiler that is good for testing:
Here we have replaced the Id
monad with the State
monad which lets us handle state in a functional and pure way. The state contains two lists, one called in
that keeps track on the input to the program, and one called out
that keeps track on, well the output of the program.
Here is an example of how to run the program now that we are using the State
monad. The main differens is that we now have to provide the initial state to the program:
The initialState contains the user input that we want to simulate. After the program has finished it’s execution we get back a representation of the state that contains the corresponding output. All very pure and immutable:
Let’s use the ScalaCheck library to generate input for us:
And that’s it, we have written a test for our program!
We have implemented a simple program using an algebra and the Free monad. A test has been written which leaves a good feeling in the stomach.
We concluded that’s it’s pretty cool that the free monad let us defer our choice of the actual monad that will be used when running the program.
If you are not tired yet I have written another post with a larger example where we use the Free monad in the context of a CLI tool and then in a web service.
The code examples are available at github.