Wednesday, 27 June 2012

Scala: Applicative with scalaz example


I’ve talked on Functor previously, will repeat myself that construction is very useful in the Scala's world. We covered Comonad as well, it is extending Functor and adds functionality for the extraction value or the wrapping of already wrapped.
This post is related to Applicative. Applicative is coupled with Functor, even has an alternative name: Applicative Functor. At first let look into class hierarchy for Applicative in scalaz library:

Inside


Starting from the simplest:
Pure: class that knows how to wrap the value: pure method accepts any type and returns P[_]. Is reasonable – if there is a way how explicitly unwrap the value (Copure) – there should be something to wrap. Pointed just mixing Pure and Functor together.
Apply: it’s better to start from refreshing what Functor does. Functor accepts v: F[A] and function: A => B as a parameters. It applies function to v and returns F[B]. Apply declares apply method that accepts Z[A] and Z[A => B]. Application of A => B to Z[A] returns Z[B]. The difference to Functor is simple: function is wrapped as well.

When

Problem: every construction should solve for the name os something, otherwise it will be forgotten;) For Applicative business is simple: Given a function that takes multiple arguments A,B,… that returns a Result, how can that function by applied to arguments M[A], M[B],… and returns M[Result]? M - can be Monad, Functor, Comand, Applicative etc functional nature wrapper. Of corse scalaz library implements this natures as ad-hoc polymorphism.
I'm going to be closer to the example: imagine situation: system reads 3 properties from files and uses the sum of 3 somewhere. Let's talk in Scala.

No sugar way

1. "Something" reads properties and assigne them to vals.
import scalaz._
import Scalaz._
val v0 = 0.some
val v1 = 1.some
val v2 = 2.some
view raw get.scala hosted with ❤ by GitHub
2. and using for construction to put them together:
val res = for (
res0 <- v0;
res1 <- v1;
res2 <- v2
) yield res0 + res1 + res2 // Some(3)
view raw for.scala hosted with ❤ by GitHub
It works well - if one from the Options is None - result is None, there is no magic, just a language construction. 3. Let's try to play via Functor map method. While map for 2 arguments, we are going even to simplify the task, add integer to 1-st Option:
val res = v0 map ((x:Int) => (y:Int ) => {x + y} ) // Option[Int => Int]
view raw add.scala hosted with ❤ by GitHub
Looks like the only one way we can proceed with map is having Option[Int => Int]. Kinda new construction for us and we might need to have an another one 'map' method that accepts M[A => B]. 4. We are not inventing the new approaches, just studying new for us ;) Here is a source code of Applicative:
trait Applicative[Z[_]] extends ... {
override def fmap[A, B](fa: Z[A], f: A => B): Z[B] = this(pure(f), fa)
override def apply[A, B](f: Z[A => B], a: Z[A]): Z[B] = liftA2(f, a, (_:A => B)(_: A))
def liftA2[A, B, C](a: Z[A], b: Z[B], f: (A, B) => C): Z[C] = apply(fmap(a, f.curried), b)
}
we already know the fmap method from Functor, and we know that apply comes from the Apply, it accepts something that we've already seen in item 3: (f: Z[A => B], a: Z[A]) and returns Z[B]. 5. Progressing with a dirty solution:
val add: (Int, Int) => Int = (x: Int, y: Int) => x + y
val addC: Int => (Int => Int) = add.curried
val map: Option[Int] => Option[Int => Int] = (in: Option[Int]) => implicitly[Functor[Option]].fmap(in, addC)
val apply1: Option[Int] = implicitly[Apply[Option]].apply(map(v0), v1)
view raw all.scala hosted with ❤ by GitHub
This code is important to understand, I'm going to describe it in 'colours':
a) add: (Int, Int) => Int is just a method declaration, that is implementing '+' synthetic function.

b) addC - we are converting add function to curried version, it will look like:
(x: Int) => (y: Int) => x + y
view raw s4.scala hosted with ❤ by GitHub
c) map method is quite long, but simple:
implicitly[Functor[Option]] - injects implicit Functor for an Option, Functor of course is
coming from the scalaz library.
Option[Int => Int] =
implicitly[Functor[Option]].fmap(in, addC) =
in map addC =
Some ( addC(in.get) )
view raw s3.scala hosted with ❤ by GitHub
Actually map is just a call curried function with internal value, and as the result we are getting partially applied function Int => Int.

It can be presented like this: d) There are:

- partially applied function: inside M[A => B]. Some[Int=>Int] = Some [{v0.get + _}]
- M[A] = M[Int]

This is exactly what apply method is expecting as parameters.

e) implicitly[Apply[Option]] - is "injected" from scalaz - surprise). Implementation is using Option's "native" map and flatMap methods. Here are detailed steps:
implicitly[Apply[Option]].apply(map(apply1), v2) =
implAppl.apply(map(apply1), v2) =
implAppl.apply(map(apply1), v2) =
map(v0).flatMap( (g: Int => Int) => v0.map(g(_: Int)) ) =
Some ((x:Int) => {x + v0.get}) flatMap ( (g: Int => Int) => v1.map(g(_: Int)) ) =
v1.map( (x:Int) => {x + v0.get} ) =
v1.map( _ + v0.get ) =
Some(v0.get + v1.get)
view raw s1.scala hosted with ❤ by GitHub
f) Finally, for the third param we are simple using:
val apply2: Option[Int] = implicitly[Apply[Option]].apply(map(apply1), v2)
view raw apply2.scala hosted with ❤ by GitHub
g) scalaz's way:
val res = v0 <*> (v1 <*> (v2 map ((x:Int) => (y: Int) => (z: Int) => x + y + z)))
view raw sugar.scala hosted with ❤ by GitHub
h) And better style:
val res = v0 <*> (v1 <*> (v2 map ((_: Int) + (_: Int) + (_: Int)).curried))
view raw sugar2.scala hosted with ❤ by GitHub

Sugar style

scalaz offers better syntax:
(v0 |@| v1 |@| v2) {_ + _ + _}
view raw s5.scala hosted with ❤ by GitHub
There is a good case example with classes, when some input (from command line or web page) should be converted into the some class instance, of course while human is involved (inputs) the result should be Option, here is a cool solution based on Applicative:
case class Order(id: Long, prodId:Long, count: Int)
val order = (some(12345l) |@| some(56789l) |@| some(1)) { Order.apply _ } // Some(Order(12345,56789,1))
view raw s6.scala hosted with ❤ by GitHub
of course result can be none if any param is missing:
val order = (some(12345l) |@| some(56789l) |@| none[Int]) { Order.apply _ } //None
view raw s7.scala hosted with ❤ by GitHub
additionally we can use a sugar for default value:
val count = (p: Option[Int]) => { p some { _.some } none {1.some} }
val order = (some(12345l) |@| some(56789l) |@| count (none[Int])) { Order.apply _ }
view raw s10.scala hosted with ❤ by GitHub

At the end:

I wasn't touching algebras during the presentation. Even for such complex type as Applicative. Usually function programming construction are described via the mess of greek symbols. I support this way is mandatory to be an subject expert, but it's not the best to start on. For future I recommend to read something on Category Theory or at least do a review for the formulas from where Applicative, Monad etc are coming to. Even scala-doc for scalaz operates with Monadic laws and other important to know facts. As a quick drop to Applicative theory: All instances of Applicative must satisfy 4 laws:
  1. identity
    forall a. a == apply(a, pure(identity))
  2. composition
    forall af ag a. apply(apply(a, ag), af) == apply(a, apply(ag, apply(af, pure(compose))))
  3. homomorphism
    forall f a. apply(pure(a), pure(f)) == pure(f(a))
  4. interchange
    forall af a. apply(pure(a), af) == apply(af, pure(f => f(x)))

And from here comes that fmap can be presented via apply and pure:
def fmap[A, B](f: A => B): F[A] => F[B] = apply(pure(f))
view raw fmap.scala hosted with ❤ by GitHub
Don' stop)

No comments:

Post a Comment