Monads as applicative functors
(If you’re not into functional programming, this article might not make much sense)
Applicative functor (or just “applicative” in short) is a more general abstraction than a monad. In other words, monad is an applicative with extra functionality, and therefore a more specialised and stronger abstraction.
Applicative can be expressed in the following three ways:
unit + apply
unit + map2
unit + map + product
By using an applicative functor instead of the non-applicative one, we can map by using multi-parameter curried functions (either by applying curried parameters one by one with apply, or by lifting everything into a product and mapping that).
Monad can be expressed in the following three ways:
unit + map + flatten
unit + flatmap
unit + compose
Since monad is an extension of the applicative functor, it must be possible to implement any applicative operations using any monadic operations.
We can demonstrate that by implementing applicative’s apply + product
using monad’s map + flatten
(we won’t be needing unit
).
Let’s say we have the following definition of a monad in Scala (we agreed to omit unit
):
trait Monad[F[_]] {
def map[A, B](fa: F[A], f: A => B): F[B] = ???
def flatten[A](fa: F[F[A]]): F[A] = ???
}
Applicative’s methods can then be implemented as:
trait Monad2[F[_]] extends Monad[F] {
def apply[A, B](fa: F[A], fab: F[A => B]): F[B] =
flatten(map(fab, (f: A => B) => map(fa, f)))
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] =
flatten(map(fa, (a: A) => map(fb, (b: B) => (a,b))))
}
Here’s an example of using apply
with a curried function, using List
for simplicity.
def apply[A, B](list: List[A], f: List[A => B]): List[B] = f.flatMap(f => list.map(f))
val curriedF = (a: Int) => (b: Int) => (c: Int) => a + b + c
val result1 = apply(List(1), List(curriedF))
val result2 = apply(List(2), result1)
apply(List(3), result2) // List(6)
The usage is the same as in applicative case. However, there’s a significant difference - if apply
is implemented using flatmap
,
we are taking advantage of monad’s power to chain things into series. Applicative functor’s apply
and product
lack this ability.
Let’s show this difference in practice. Since we were working with apply
in the previous example, let’s take product
now.
We can provide two different implementations: one using monadic functions, and another one in standard applicative fashion (we can use standard Scala’s zip
):
// Future stuff
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
// Monad
def serialProduct[A, B](f1: => Future[A], f2: => Future[B]): Future[(A, B)] =
f1.flatMap(a => f2.map(b => (a, b)))
// Applicative
def parallelProduct[A, B](f1: => Future[A], f2: => Future[B]): Future[(A, B)] =
f1 zip f2
Note that both are taking their parameters by-name instead of by-value in order to prevent futures from executing as soon as they are passed.
Let’s create the futures:
def first = Future { println("first"); Thread.sleep(2000); 1 }
def second = Future { println("second"); Thread.sleep(2000); 2 }
Using serial product, we get:
val result1 = serialProduct(first, second).map { case (a, b) => a + b }
Await.result(result1, 10 seconds)
// prints "first", then two seconds later "second"
Using parallel product, we get:
val result2 = parallelProduct(first, second).map { case (a, b) => a + b }
Await.result(result2, 10 seconds)
// performs in parallel; order of printing is indeterministic
Monad as a data structure could have methods apply
and product
exposed in its API, but it usually doesn’t.
If you need those operations on some type T
, your code should restrict T
to the Applicative
type class, following the “rule of least power”.
However, it is common to end up working with monads in conjunction with applicatives, in which case it can be tedious to juggle between the two.
In that case, some libraries also provide the “bridge” type class (in Cats it’s intuitively called Parallel)
which abstracts over Monad
type class by providing it with parallel applicative functionality.