Note: If you jumped to this page because you’re interested in applicative functors, you should definitely read Semigroupal and Apply first.
Functors, Applicative Functors and Monoids:
Meet the
Applicative
typeclass. It lies in theControl.Applicative
module and it defines two methods,pure
and<*>
.
Let’s see Cats’ Applicative
:
@typeclass trait Applicative[F[_]] extends Apply[F] { self =>
/**
* `pure` lifts any value into the Applicative Functor
*
* Applicative[Option].pure(10) = Some(10)
*/
def pure[A](x: A): F[A]
....
}
It’s an extension of Apply
with pure
.
LYAHFGG:
pure
should take a value of any type and return an applicative value with that value inside it. … A better way of thinking aboutpure
would be to say that it takes a value and puts it in some sort of default (or pure) context—a minimal context that still yields that value.
It seems like it’s basically a constructor that takes value A
and returns F[A]
.
import cats._, cats.syntax.all._
Applicative[List].pure(1)
// res0: List[Int] = List(1)
Applicative[Option].pure(1)
// res1: Option[Int] = Some(value = 1)
This actually comes in handy using Apply[F].ap
so we can avoid calling {{...}.some}
.
{
val F = Applicative[Option]
F.ap({ F.pure((_: Int) + 3) })(F.pure(9))
}
// res2: Option[Int] = Some(value = 12)
We’ve abstracted Option
away from the code.
LYAHFGG:
Let’s try implementing a function that takes a list of applicatives and returns an applicative that has a list as its result value. We’ll call it
sequenceA
.
sequenceA :: (Applicative f) => [f a] -> f [a]
sequenceA [] = pure []
sequenceA (x:xs) = (:) <$> x <*> sequenceA xs
Let’s try implementing this with Cats!
def sequenceA[F[_]: Applicative, A](list: List[F[A]]): F[List[A]] = list match {
case Nil => Applicative[F].pure(Nil: List[A])
case x :: xs => (x, sequenceA(xs)) mapN {_ :: _}
}
Let’s test it:
sequenceA(List(1.some, 2.some))
// res3: Option[List[Int]] = Some(value = List(1, 2))
sequenceA(List(3.some, none[Int], 1.some))
// res4: Option[List[Int]] = None
sequenceA(List(List(1, 2, 3), List(4, 5, 6)))
// res5: List[List[Int]] = List(
// List(1, 4),
// List(1, 5),
// List(1, 6),
// List(2, 4),
// List(2, 5),
// List(2, 6),
// List(3, 4),
// List(3, 5),
// List(3, 6)
// )
We got the right answers. What’s interesting here is that we did end up needing
Applicative
after all, and sequenceA
is generic in a typeclassy way.
Using
sequenceA
is useful when we have a list of functions and we want to feed the same input to all of them and then view the list of results.
For Function1
with Int
fixed example, we need some type annotation:
{
val f = sequenceA[Function1[Int, *], Int](List((_: Int) + 3, (_: Int) + 2, (_: Int) + 1))
f(3)
}
// res6: List[Int] = List(6, 5, 4)
Here are the laws for Applicative
:
pure id <*> v = v
pure f <*> pure x = pure (f x)
u <*> pure y = pure ($ y) <*> u
Cats defines another law
def applicativeMap[A, B](fa: F[A], f: A => B): IsEq[F[B]] =
fa.map(f) <-> fa.ap(F.pure(f))
This seem to say that if you combine F.ap
and F.pure
, you should get the same effect as F.map
.
It took us a while, but I am glad we got this far. We’ll pick it up from here later.