"Monad" is a word that describes a set of behaviors
In scala, we use the Monad[Foo]
typeclass from cats to define instances of this behavior.
The essence of its behavior is the ability to describe a series of computations, where one computation depends on the result of the computation that came before it.
For example, Monad[Option]
shows that the Option[A]
data type can be used to describe computations of A
which may result in no value.
case class Comment(text: String, author: Option[String])
case class User(name: String, email: String)
def getUser(name: String): Option[User] = ???
for {
author <- comment.author
user <- getUser(author)
} yield user.email
The above code shows an example of a dependent computation. Given a comment, we can maybe get the author. If we have the author, we can maybe look up a user. If we have the user, we can show their email.
We can write a method using the Monad
interface to describe a computation which is valid for any data types that allow us to do sequential operations.
Monadic data structures allow us to abstract over control flow.
def oneThenAnother[F[_]: Monad, A, B, C](f1: A => F[B])(f2: B => F[C]): A => F[C] =
(a: A) => f1(a).flatMap(b => f2(b))
The oneThenAnother
method can be used on any data structure that allows monadic behavior.
When we use Monad[Option]
, we abstract over control flow where at any step can stop with no result
When we use Monad[Either[Throwable, *]]
, we abstract over control flow where any step can fail with a Throwable
When we use Monad[IO]
, we can abstract over computations that may be either synchronous or asynchronous and which are able to perform arbitrary actions during their execution.
Writing code using monads allows us to separate the concerns of control flow from the specific computations.