On previous projects, our team used a combination of Either and Maybe monads as return values for our external (IO-based) operations. However, when asked recently by my pair on a new team (and project) I was unable to describe what makes a monad a monad.
After doing some research, I came across this analogy that helped me better understand monads.
A monad is an abstract data type that allows programmers to chain complex, nondeterministic operations.
Let’s talk some theory…
An abstract data type (ADT) is a kind of data type defined by it’s behavior from the point of view from a user. Since an ADT is created from the user’s point of view, it’s internal presentation is hidden, thus allowing us to focus more on behavior than how the data is stored or accessed.
The ability to chain operations is the functor attribute of monads. In category theory, a functor is a something has enables mappable behavior, such as mapping over a list of objects. To enable the chaining of nondeterministic operations, two functions are found on monad types: return and bind.
The return function places a value into a monadic context, whereas the bind function applies a function in a monadic context.
Note: Depending on the programming language or library that you are using, the return function will typically be represented through the constructor method and bind may have a different name such as flatMap or map.
Let’s put this theory to practice…
The Either monad is a monadic data type that allows you to handle either one possible state or another possible state. We’ll be using the Scala programming language for our example code. The Either monad is part of Scala’s standard library, so we won’t have to pull in a third-party (or build our own) Either library.
val countries: List[Either[Error, String]] = List( Right("United States"), Left(new Error("Country not found: Rusia")), Right("United Kingdom"), Right("China"), Left(new Error("Country not found: Paraguy")) )
In the example above, we create a list of wrapped Errors or Strings (representing countries). When we pass our country into the Right or Left functions, equivalently return, we are effectively wrapping our value inside the Either monad container.
... val logException: (e: Error) = println(s"Unexpected error occurred: $error"); val availableCountries = countries .map(_.right.map(_.toUpperCase)) .map(_.left.map(logException))
Continuing from our previous example, we create a logException function to print error messages and then we do some transformations on our list of countries.
When we map, equivalently bind, over our list, we gain the ability to safely access the Left or Right values from the list of counries. If we have any Right values then we apply the toUpperCase function to all of our Right values. If we have any Left values then we will log exceptions.
Please send me an email if I’ve missed anything along the way. Thank you for reading!