Applicative 

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 the Control.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 about pure 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.

Useful functions for Applicative 

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)

Applicative Laws 

Here are the laws for Applicative:

  • identity: pure id <*> v = v
  • homomorphism: pure f <*> pure x = pure (f x)
  • interchange: 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.