Innovation
“Monad comprehension” is a scary term. Here’s how it redefined our workflow.
By: Ben Holmes, Software Engineer
Ah yes. The dreaded monad. A term that strikes fear into the FP novice and budding mathematician alike. Some claim to have the secret to enlightenment that even beginners can understand… but they seem to get lost in jargon along the way.
So, this post is not going to define what a monad is. We’re not even going to touch “monads” as a topic directly. Instead, we’re going to explore an extremely helpful pattern for failing fast and avoiding returns with comprehension! We’ll touch on:
🌊 How a sea of if(...) return gets unwieldy
✋ What we can learn from async / await
🤷♂️ Exploring ArrowKT’s Either type
🧵 How it all ties into monad comprehension
Alright, let’s ride!🚴♀️
The problem: stop-and-go traffic.
If you’ve worked on backends before, you’ve probably run into a pattern like this before:

You see what’s going on here. We have a bunch of operations to do, and every one is possibly unsafe! Yes, if statements like these are quick to whip up. But I should point that only 3 lines of this code are actually important for humans to read. The other 9 are just safety checks 😬
We don’t wanna get caught in if statement traffic. We should be free to blast down our highway of code!
How async / await overcame a similar problem
If you haven’t used JavaScript in the last few years, you may have missed this new handler for asynchronous events. Originally, it wasn’t possible to handle the result of something that came back later without defining a callback function. This can lead to a sea of indentions called “callback hell:”

This is pretty difficult to read. It should really be a simple sequence of events:
authenticateUser -> getUserPreferences -> getPreferredUserEmail -> sendPromoToEmail
…but all the nested functions obscure the flow. Luckily, a new format has come along to make everything sequential:

Nice! Our code looks a lot closer to our original goal. All we need to do is
Wrap everything in a function with an async decorator. This tells JavaScript that this function might contain some async code it’ll have to wait on.
Preface any asynchronous code with await. This means our current line of code could take some time before it finishes, so we should “wait” before moving on.
It’s a neat pattern that other languages are picking up as well. But for a line of thinking as clever as this… could we expand it to other concepts as well?
Our dream
Let’s revisit that example from earlier. Just for fun, let’s invent a magical new operator reminiscent of async / await, called returnIfNull.
This operator will check the result of whatever comes after it. If that result happens to be null, it’ll short circuit from whatever function you’re currently in.

Looks pretty neat! But of course, you can’t define helpers like this on a whim. We need some kind of interpreter to comprehend this sort of syntax.
Enter: Kotlin + ArrowKT
Yes, we’ll be switching over to Kotlin to explore this “short circuit” concept. No, this isn’t the only language that can pull it off… but since it’s the language used across our microservices here at Peloton, it seems like a perfect fit for this article!
Before going any further, it’s worth a little explanation of what ArrowKT’s Either type is. In short: it’s a lot like “null,” but it lets you attach an error type to explain why something is “null.”
Let’s take this fetchProductWarranty function as an example. Don’t worry too much about the Kotlin syntax; Just read the comments to understand the big takeaways.
With a regular nulls:

With an Either type

Woah, there’s some fancy stuff going on here! Here’s some major takeaways:
Either types have a “left” and “right” side. Only one of these sides can have a value at a given time. In this case, either we fill in the right side with our successful warranty result, or we fill the left side with an enum representing the error type
Exceptions are represented by concrete types, not randomly exploding throwables. This makes it much clearer what fetchProductWarranty could actually return. If we used null, we couldn’t return a descriptive error to the client. And if we just abandoned the try / catch entirely, anyone calling the function could forget to catch the bomb! We have a whole article on this concept over here, so head over there if you’re curious how you could ditch null in your language of choice 🔥
“Comprehending” Eithers
Alright, now we have a fancier way of representing null. So how does this apply to our returnIfNull proposal?
Well, Either clearly isn’t just a null pointer. It’s an all-new type provided by the ArrowKT library, with all sorts of helpers and goodies to enhance its usefulness.
Let me throw some sample code at you to blow your mind:

Guess what? This code acts the same way as our if statement traffic from earlier! There’s 2 major pieces that make this magic possible:
We define an Either “context” using lowercase either<Error, Success>. This basically means hey, we have some functions in here that return Either types. Let’s handle their error cases in a clean way.
We “unwrap” anything that returns an Either using the ! operator. For instance, say fetchProductWarranty returns either a Warranty or a DBErrors enum as before. If Warranty happens to come back, we’ll assign it to our productWarranty variable. Otherwise, we’ll short circuit from the whole function, returning our DBErrors enum from getProductBundle
This isn’t too much different from our async / await syntax. we’re just replacing async with either<Left, Right>, and replacing await with our ! operator.
We just used monadic comprehension
This whole idea of “comprehending” Eithers and asynchronous tasks falls into the monadic comprehension bucket. In our case, the Either is our monad, and our use of that either block + the ! is our comprehension.
This means you could define any fancy class (monad) that you want! As long as you follow the same general pattern, you’re good to go. ArrowKT has a whole article on how powerful this concept can be, so definitely check that out if you’re diving into Kotlin yourself!
It also nests as deeply as you want
Defining context also lets us throw these ! unwrappers anywhere we want. For instance, say we’re looping over an array of Either types, and we want to short circuit if one of them has an error. This works exactly how you’d expect:

You can also unwrap just-in-time, like when assigning function or constructor params:

As long as there’s some either<Left, Right> {...} block to bubble up to, the error will get “caught” as you’d expect.
(Un)wrapping up
I hope this shows you how powerful the idea of “monad comprehension” can be. If Kotlin isn’t your language of choice, here’s some comprehension-related resources we’ve found on from around the blogosphere:
Thanks for reading!