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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scalaz._ | |
import Scalaz._ | |
val v0 = 0.some | |
val v1 = 1.some | |
val v2 = 2.some |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
val res = for ( | |
res0 <- v0; | |
res1 <- v1; | |
res2 <- v2 | |
) yield res0 + res1 + res2 // Some(3) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
val res = v0 map ((x:Int) => (y:Int ) => {x + y} ) // Option[Int => Int] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(x: Int) => (y: Int) => x + y |
implicitly[Functor[Option]] - injects implicit Functor for an Option, Functor of course is
coming from the scalaz library.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Option[Int => Int] = | |
implicitly[Functor[Option]].fmap(in, addC) = | |
in map addC = | |
Some ( addC(in.get) ) |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
val apply2: Option[Int] = implicitly[Apply[Option]].apply(map(apply1), v2) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
val res = v0 <*> (v1 <*> (v2 map ((x:Int) => (y: Int) => (z: Int) => x + y + z))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
val res = v0 <*> (v1 <*> (v2 map ((_: Int) + (_: Int) + (_: Int)).curried)) |
Sugar style
scalaz offers better syntax:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(v0 |@| v1 |@| v2) {_ + _ + _} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
case class Order(id: Long, prodId:Long, count: Int) | |
val order = (some(12345l) |@| some(56789l) |@| some(1)) { Order.apply _ } // Some(Order(12345,56789,1)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
val order = (some(12345l) |@| some(56789l) |@| none[Int]) { Order.apply _ } //None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
val count = (p: Option[Int]) => { p some { _.some } none {1.some} } | |
val order = (some(12345l) |@| some(56789l) |@| count (none[Int])) { Order.apply _ } |
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:- identity
forall a. a == apply(a, pure(identity))
- composition
forall af ag a. apply(apply(a, ag), af) == apply(a, apply(ag, apply(af, pure(compose))))
- homomorphism
forall f a. apply(pure(a), pure(f)) == pure(f(a))
- 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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def fmap[A, B](f: A => B): F[A] => F[B] = apply(pure(f)) |
No comments:
Post a Comment