Functors, Applicative Functors and Monoids:
So far, when we were mapping functions over functors, we usually mapped functions that take only one parameter. But what happens when we map a function like
*
, which takes two parameters, over a functor?
scala> import cats._, cats.data._, cats.implicits._
import cats._
import cats.data._
import cats.implicits._
scala> val hs = Functor[List].map(List(1, 2, 3, 4)) ({(_: Int) * (_:Int)}.curried)
hs: List[Int => Int] = List(<function1>, <function1>, <function1>, <function1>)
scala> Functor[List].map(hs) {_(9)}
res6: List[Int] = List(9, 18, 27, 36)
LYAHFGG:
But what if we have a functor value of
Just (3 *)
and a functor value ofJust 5
, and we want to take out the function fromJust(3 *)
and map it overJust 5
?Meet the
Applicative
typeclass. It lies in theControl.Applicative
module and it defines two methods,pure
and<*>
.
Cats splits this into Cartesian
, Apply
, and Applicative
. Here’s the contract for Cartesian
:
/**
* [[Cartesian]] captures the idea of composing independent effectful values.
* It is of particular interest when taken together with [[Functor]] - where [[Functor]]
* captures the idea of applying a unary pure function to an effectful value,
* calling `product` with `map` allows one to apply a function of arbitrary arity to multiple
* independent effectful values.
*
* That same idea is also manifested in the form of [[Apply]], and indeed [[Apply]] extends both
* [[Cartesian]] and [[Functor]] to illustrate this.
*/
@typeclass trait Cartesian[F[_]] {
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
}
Cartesian defines product
function, which produces a pair of (A, B)
wrapped in effect F[_]
out of F[A]
and F[B]
. The symbolic alias for product
is |@|
also known as the applicative style.
Before we move on, let’s look at the syntax that Cats adds to create an Option
value.
scala> 9.some
res7: Option[Int] = Some(9)
scala> none[Int]
res8: Option[Int] = None
We can write (Some(9): Option[Int])
as 9.some
.
LYAHFGG:
With the
Applicative
type class, we can chain the use of the<*>
function, thus enabling us to seamlessly operate on several applicative values instead of just one.
Here’s an example in Haskell:
ghci> pure (-) <*> Just 3 <*> Just 5
Just (-2)
Cats comes with the CartesianBuilder
syntax.
scala> (3.some |@| 5.some) map { _ - _ }
res9: Option[Int] = Some(-2)
scala> (none[Int] |@| 5.some) map { _ - _ }
res10: Option[Int] = None
scala> (3.some |@| none[Int]) map { _ - _ }
res11: Option[Int] = None
This shows that Option
forms Cartesian
.
LYAHFGG:
Lists (actually the list type constructor,
[]
) are applicative functors. What a surprise!
Let’s see if we can use the CartesianBuilder
sytax:
scala> (List("ha", "heh", "hmm") |@| List("?", "!", ".")) map {_ + _}
res12: List[String] = List(ha?, ha!, ha., heh?, heh!, heh., hmm?, hmm!, hmm.)
Cartesian
enables two operators, <*
and *>
, which are special cases of Apply[F].product
:
abstract class CartesianOps[F[_], A] extends Cartesian.Ops[F, A] {
def |@|[B](fb: F[B]): CartesianBuilder[F]#CartesianBuilder2[A, B] =
new CartesianBuilder[F] |@| self |@| fb
def *>[B](fb: F[B])(implicit F: Functor[F]): F[B] = F.map(typeClassInstance.product(self, fb)) { case (a, b) => b }
def <*[B](fb: F[B])(implicit F: Functor[F]): F[A] = F.map(typeClassInstance.product(self, fb)) { case (a, b) => a }
}
The definition looks simple enough, but the effect is cool:
scala> 1.some <* 2.some
res13: Option[Int] = Some(1)
scala> none[Int] <* 2.some
res14: Option[Int] = None
scala> 1.some *> 2.some
res15: Option[Int] = Some(2)
scala> none[Int] *> 2.some
res16: Option[Int] = None
If either side fails, we get None
.
Cartesian
has a single law called associativity:
trait CartesianLaws[F[_]] {
implicit def F: Cartesian[F]
def cartesianAssociativity[A, B, C](fa: F[A], fb: F[B], fc: F[C]): (F[(A, (B, C))], F[((A, B), C)]) =
(F.product(fa, F.product(fb, fc)), F.product(F.product(fa, fb), fc))
}