Type Class 101: ApplicativeBuilder

Today we talk about the little wiggly operator |@|. Being slightly deaf and one of the few living persons on this planet who never watched a Star Wars movie, I always thought people call this the allah al akbar operator, when in fact it is called the Admiral Ackbar operator. If you haven’t so far, please read the post about Applicatives as this blog post builds on top of it.

Applicatives as alternatives to for comprehensions

Say we have some values in an Option, want to retrieve that value and create a case class instance from that, which is again optional, than using the monadic for comprehension we could write:

case class ABC(a: Int, b: Int, c: Int)

val result : Option[ABC] = for {
  a <- aOpt
  b <- bOpt
  c <- cOpt
} yield { ABC(a,b,c) }

This is all fine and dandy, and a common pattern in Scala code. For comprehensions are actually translated into calls of flatMap, map and filter (more on that when we cover Monads) and one property of this translation is that it is sequential. Despite the fact, that we could calculate a,b and c independently, we don’t.

Applicatives have some nice properties, which makes the code above slightly “better”. We could for example write:

Applicative[Option].apply3(aOpt,bOpt,cOpt)((a,b,c) => ABC(a,b,c))
Applicative[Option].apply3(aOpt, bOpt, cOpt)(ABC.apply)
Applicative[Option].apply3(aOpt, bOpt, cOpt)(ABC)

All 3 examples are totally equal. With Applicative[Option] we get the applicative instance which is in scope, then we collect our function parameters and pass a function to be applied to these parameters. As a case class already provides a function to create an instance of this case class we can also just pass apply and are done.

The not so nice thing about this is, that we need to write Applicative[Option] and that we need to keep track of the number of parameters passed to applyN. So we want to get rid of the Applicative[Option] call and use some kind of usual syntax-style and reduce the noise a bit.

This can actually be done like this:

(aOpt |@| bOpt |@| cOpt){ABC}

|@| is the operator of the ApplicativeBuilder - a temporary data structure which collects arguments to be applied to an Applicative. It just supports a nicer syntax. Compared to for comprehension above we get a cleaner syntax, for example because we do not need to name temporary parameters like a,b,c and can apply a function (which we already have) directly.

Play, JSON and Applicatives

On the off chance that you have been using the Play Framework you will probably already have used the JSON combinators, like so:

case class ABC(a: Int, b: Int, c: Int)

val abcReads: Reads[ABC] = (
  (__ \ "a").read[Int] and
  (__ \ "b").read[Int] and
  (__ \ "c").read[Int]
) { ABC.apply _ }

Hmm, that looks very much like our beloved Applicative and and like the |@| operator. And indeed, if you look around in the play documentation, you find that it in order to use the the and operator, you need to include the import import play.api.libs.functional.syntax._. Considering this, it makes sense: we can read the JSON structure if all the independent values yield a reasonable result, and if they do, apply the function and if they don’t give us a list of errors (which is ommited here in the example).

We could implement an instance of our Applicative for Reads[], but on the other hand the people from play already did that. Only in the wrong package, with another definition for their Applicative and ApplicativeBuilder. How could we easily transform their implementation into our implementation without repeating too much code?

Natural Transformations

A Natural Transformation basically defines a function from one typeconstructor F[_] to another typeconstructor G[_] and preserves the structure:

trait NaturalTransformation[-F[_], +G[_]] {
  def apply[A](F: F[A]): G[A]
}

Compare this with the definition of the trait for a function (!):

trait Function1[-T1, +R] {
  def apply(v1 : T1) : R
}

Frequently there is a type alias defined for a Natural Transformation:

type ~>[F[_], G[_]] = NaturalTransformation[F, G]

Using this structure we could for example transform any monoid which is defined in play, into a monoid in simplez (or scalaz):

import play.api.libs.functional.{ Applicative => PlayApplicative, Monoid => PlayMonoid, _ }

...

  // transform any play monoid to a simplez monoid
object PlayMonoid2Monoid extends (PlayMonoid ~> Monoid) {
    def apply[A](playM: PlayMonoid[A]): Monoid[A] = new Monoid[A] {
    def zero: A = playM.identity
    def append(a: A, b: A): A = playM.append(a, b)
  }
}

// transform any concrete play monoid instance into the concrete simplez monoid instance
// using the natural transformation ~>
implicit def playMonoid2Monoid[A](implicit P: PlayMonoid[A], ev: PlayMonoid ~> Monoid): Monoid[A] = ev(P)

...

However for an applicative this won’t work - the structure of a Monoid is Monoid[A] (hence we can use the Natural Transformation PlayMonoid ~> Monoid), but an Applicative has the structure Applicative[F[_]] - we therefore need some kind of higher order transformation:

abstract class NaturalTransformation2[-F[_[_]], +G[_[_]]] {
  def apply[M[_]](F: F[M]): G[M]
}

type ~~>[F[_[_]], G[_[_]]] = NaturalTransformation2[F, G]

and can finally write:

  // transform any play applicative into a simplez applicative
  object PlayApplicative2Applicative extends (PlayApplicative ~~> Applicative) {
    def apply[M[_]](F: PlayApplicative[M]): Applicative[M] = new Applicative[M] {
      def pure[A](a: A) = F.pure(a)
      def ap[A, B](fa: => M[A])(f: => M[A => B]): M[B] = F.apply(f, fa)
    }
  }

  // transform any concrete play applicative instance into a simplez applicative instance
  // using a higher kinded "natural transformat" ~~>
  implicit def playApplicative2Applicative[F[_]](implicit P: PlayApplicative[F],
    ev: PlayApplicative ~~> Applicative): Applicative[F] = {
      ev(P)
  }

and arrive at:

implicit val abcReads : Reads[ABC]= (
(__ \ "a").read[Int] |@|
(__ \ "b").read[Int] |@|
(__ \ "c").read[Int]) { ABC.apply _ }

For more transformations of type classes between play and scalaz have a look at Play2Scalaz. There is more to Applicatives, and I’ll come back to that eventually.

For further articles in this series: TypeClass101

Kommentare