Free Monads in Web Stack — Part I
How to use Free Monads in Scala web applications
At Iterators we’ve had many ideas on how to build a well-structured REST application and we are constantly searching for improvement and better ways to get the job done. Recently we built a system centered around free monads. We want to share this bit of knowledge in the hope that you’ll find it inspiring, or maybe show us how to do it better. While there are lots of tutorials on
Free itself, we didn’t find many that explain how to use it as a kind of glue layer in your system. All in all, your comments are more than welcome.
This post will not teach you what free monads are. There are a lot of materials on the Internet explaining the concept, and I’ll assume that you already know what `Free` is. In case you need a refresher, read this or this.
Let’s build a Free app (neither free as in freedom, nor free as in free beer)
By app I mean full-blown akka-http, slick, cats system with tests. Obviously we’ll keep it simple, but without skipping any important details. If you want to play with the code on your own, you can get it from GitHub. Let’s say this app will be everyone’s favorite Wombat Cuddler Job App — an app for applying for a Wombat Cuddler position on Flinders Island (and no, we didn’t build an app like that. Shame).
So let’s start with a simple template, as we usually do — the entry point to our application.
… And now what? Well, our advice is this:
If you want to build your app around Free, you should start with use-cases — business logic, if you will — and turn these into ADT that you will later write interpreters for.
Forget about services, repositories, and routers. You need to build your language on which you will add all the infrastructure a modern app is going to need.
So, having said that, what’s our use case? Obviously, we need to handle job applicants who are dying to apply for this dream job. Now, we really want a systematic app organization, so let’s go with, say,
com.theiterators.wombatcuddler.actions — the package where you’ll keep your functional gems. Let’s start simple and abstract at the same time: by creating
Cuddlers trait where we’ll be describing actions that a (prospective) cuddler can perform. We want to register applications from wannabe cuddlers, preferably by accepting some form of request with all the needed bits, validate if this is a unique application (otherwise everyone would be flooding you with their requests), save it to some sort of storage and return, say, a public identifier known only to the one who’s just applied with which he can update his application later on, etc. Easy enough, but we really have to express this “action” by combination of primitive “instructions” (that is, case classes) representing our DSL. That is why we need to form a base language and then write a program in that language (for now, not worrying about the interpretation).
Validations are static things — they take a request and return something along the lines of
Either[Error, Request], so we may want to keep them as simple methods.
What we need to abstract out are operations that we do not yet know how to execute (that will be the job of interpreter) — save request, update request, possibly remove request (if someone is insane enough to resign). Let’s see what it looks like:
Notice a little helper type
execute method — they will help us to write fine DSL to describe our programs. Let’s write our first simple program. In terms of our primitives, we need to validate request and call the abstract
Why Either everywhere?
We need to embrace failure. All these actions that we’ll have to implement at some point can fail (they’ll probably be using a
DBIOor whatever), so it’s crucial to make it easy to signal and handle failure in abstracted logic. We, being functional programming advocates, do not really like exceptions. Besides, you can’t really use exceptions in Free because our little programs just build a structure describing a computation that’s executed somewhere else. If the program threw an exception, it’d manifest itself during interpretation, greatly surprising a programmer writing an interpreter. So your only choice is to handle failure by wrapping it in an appropriate data structure. There are other obvious advantages of this approach:
* it’s type-safe,
* it’s explicit,
* you do not need to write any error-handling code yourself (see below)
In parallel to building a business-level DSL, we’re building our neat “internal” DSL for dealing with Free as a metaphor of “program”. I hope it reads well.
We can, for instance, lift values to
Free by calling
pure. This way we’ll save our future interpreter a bit of work — it won’t have to validate requests (and any interpreter will benefit from that).
Free.pure is very much like immediate
return without executing anything, so we can call it, for instance,
OK, that was easy, let’s do
update. The first trouble is that our language is not sufficient to do the job — we’d like to check if email matches a PIN here (to save interpreters some work, remember) — but since these are opaque operations we have no ways to do that. We’ll need to extend our language. Also, since there are a bunch of
Eithers we need to handle, we could really save ourselves some trouble and wrap our
Program (which is Free, which is a monad :-)) in
EitherT to handle for us failing the program if something’s gone wrong. And what does this “handling for us” mean? Whenever a
Left(x) is processed, the program stops its execution and returns that to its caller.
We’re going to create a type alias over this
type ProgramThatHandlesErrorsAutomatically[A] = EitherT[Program, Error, A]. While the name above surely reflects what it does, it is a bit unwieldy, so let’s call it
ProgramEx instead :-).
To fully use the techniques presented from now on, add
addCompilerPlugin(“com.milessabin” % “si2712fix-plugin” % “1.2.0” cross CrossVersion.full)
to your build file. You could probably avoid it if you really had to, but I see no point in tormenting yourself by fighting dumb stock compiler.
Why do we need EitherT stuff?
EitherT is a so-called monad transformer. You can read about monad transformers in-depth here or here. We could have used
Program[Either[Error, A]] all the way though. But you’d quickly discover it becomes really unwieldy since
Program doesn’t know it operates on
Eithers, so it would have been up to you to unwrap it from within your program flow and, based on its value, fail or proceed.
EitherT, being monad transformer, not only preserves all properties of its underlying monad, but also, knowing that it can expect only
Either values, can eliminate this boilerplate by making the correct decision for you. In a way, it combines ‘fail-fast’
Either behavior with
Free (or any other) monad behavior. An equivalent program written without
EitherT would look like a mess (I’m not even sure if it compiles):
A piece of advice stemming from what we’ve learnt so far:
Always use monad transformers with Free monads — they go well together. But do not write things like
EitherT.fromEither(req.validate)in your programs — it quickly becomes line noise. Wrap transforming operations in internal DSL and you’ll achieve clean flow.
If your program is Free, then your program wrapped in EitherT is the equivalent of the program “throwing exceptions” (but in a functional way). In other words, use EitherT if you want to bail out at the first error.
At this point we’ve achieved a thing we would kill for at Iterators — our code is testable right away. That should come as no surprise if you read about Free monads — that’s one of their main selling points, after all. You do not need a database, model; heck, you do not even need a mock library. You can just write an interpreter for your program and see if you got the logic right. Like this:
So, you use a bare-bones
foldMap, write a completely bogus interpreter (a mock, if you will, but without all these dark ceremonies of
returning and whatnot), fake everything (even types) — but in the end you can see if your logic is right. It becomes more and more important as the logic becomes more twisted that you can test it at any time with minimal effort.
That’s a very desirable property, but Free monads can offer you much more. Stay tuned for the second part of the series, where I’ll show how we built our good ol’ Service layer and how to tie it up with HTTP resource. When we finally get an end-to-end example, we’ll complicate matters even further…