And the Result is a monad

George Theocharis
5 min readMar 21, 2021

Adding superpowers to a custom type

In modern Android and with the advent of Kotlin, functional programming is integrating into our everyday code bit by bit. Do we take full advantage of this paradigm though? In this article, I will explain, how a relative common nowadays type like Result<T> can be “powered up” to become a monad and help via composition, to solve different problems.

What is a monad ❓
To begin with, let us see what a monad is. Wikipedia, states as a definition:

In functional programming, a monad is an abstraction that allows structuring programs generically. Supporting languages may use monads to abstract away boilerplate code needed by the program logic. Monads achieve this by providing their own data type (a particular type for each type of monad), which represents a specific form of computation, along with one procedure to wrap values of any basic type within the monad (yielding a monadic value) and another to compose functions that output monadic values (called monadic functions).[1]

But what does it actually mean? In Layman’s terms, think about it like creating a type that wraps other types and adds some superpowers to it. For example, the Option<T> is a famous monad that showcases the optionality of a type by having the values of Some(T) and None. When the value is an instance of Some then that indicates that the value exists while in None the value is absent.

Now that we have a rough picture of monads in our mind, let’s see the next ingredient to our functional recipe which is none other than the Result<T>.

Introduction to Result 📥
Result is a famous type in Android that is commonly used as a sealed class with two subclasses Success and Error . An example of this can be found in the below gist:

The Result Type ☀️

The result type is usually used in asynchronous return types. For example, when having a typical API call that can succeed or fail due to any reason. But that is not its only usage option, as we can leverage the type, to handle other domain needs, like validation, or anything in reality that can succeed or fail.

The pitfall of doing this is the constant checking that the type needs to see if something is actually a success or an error, making it cumbersome to work with. Alas if that part was omitted and not needed, wouldn’t that be much better and convenient? So let’s not spend our time and add some superpowers to our type, transforming it essential to a Monad.

Morphing Time ♻️
So what do we need in order for our type to be “elevated” to a monad? First of all, we need the following operations defined:

  • A return function that can quickly and easily lift an already existing type to our result.
  • A bind function that acts on a Result<T> , extracts T and applies a lambda to it, returning a Result<R>.

An easy way to create the return function, is to have a method defined in the companion like the following gist. Due to the return word being a language keyword we can rename it to of and make it part of the companion object.

Building Results 🚧

With the above snippet and taking advantage of Kotlin’s trailing lambda, we can transform anything now safely to a result.

An example would be val result = Result.of { api.makeCall() }

Now let’s see about bind and how we can model it.

Bind or FlatMap 💨

Leveraging the awesome feature of extension methods in Kotlin, we define an extension to an already existing result that if it’s a success we unwrap it, apply a function to it and wrap it back to success. This looks similar to flatMap and it actually is, so you can go ahead and rename it as such.

Are we done yet? Kinda! What remains is for us to prove that our new “monad” actually adheres to the monad laws. So let’s go prove that our type is a monad!

Monad Laws 📜

There are 3 monad laws we have to confirm that our type satisfies in order to be fully considered a monad and these are: Right identity, left identity, and associativity.

  • Right identity means that applying flatMap by passing in the Result.of as the expected action, then that means that we should end back to where we started. In other words, if for example, we have a val result = Result.of{ 5 } then by doing result.flatMap { value -> Result.of { value } } it will also produce Success(5).
  • Left identity is kind of the opposite of right identity and that means that we have to prove the following: Result.of(value).flatMap(::anyMethodThatReturnsResult) == anyMethodThatReturnsResult(value) which is expressed as flattening a result and applying to its value a method is the same as applying the same method to the value in the first place.
  • Associativity means that the order or the nesting in this example of the flat maps does not alter the resultresult.flatMap(::method1).flatMap(::method2) == result.flatMap { method1(x).flatMap(::method2) } similar to addition and how (1 + 3) + 2 is equivalent to (3 + 2) + 1.

All the above laws can be easily tested with simple JUnit tests.

Testing the laws 🚀

Superpowers 💪

Now that everything is in place the power of it is already being shown. Our result monad can be used not only for asynchronous calls but for any complex business case. Furthermore, it can be extended with more functionality as an orElse method that will handle the error paths, or a map method that can simply unwrap the result type but let's see an example as it is much better than describing it.

A more complex scenario would be the business logic behind registering a user. Imagine if for registering, we needed an age over 18, a non-empty email, and a password greater than 8 chars. All this could be easily handled in a result chain.

As you can see this can be extended and used in many many ways and it can be very helpful. One change could be to use domain errors instead of a simple string. The above are just ideas and you are free to mix and match to find the preferred solution according to any business needs.

Although all the above are quite simple and easy to use or build upon, one can also use an already established monad like Either from Arrow that is worth experimenting with and trying out!

Thanks for reading this and hopefully found it interesting!❤️

--

--

George Theocharis

Passion driven software engineer with the mission to constantly provide value, while crafting a unique story along the way. Android developer at heart.