Tuesday 29 May 2012

Rework: Monads in Scala - 2

During previous session we captured in mind 3 transformations/conversions. While moving out from mathematical pressure we just got 3 simple formulas:


a) (A => B ) => (M[A] => M[B]) // Monad
b) (A => M[B] ) => (M[A] => M[B]) //Functor
c) (M [A => B]) => (M[A] => M[B]) //Applicative


As an implementation we've chosen functional way:

a) Func(A => B) : M[A] => M[B] // Monad
b) Func(A => Box[B]) : Box[A] => Box[B] //Functor
c) Func(Box[A => B]) : Box[A] => Box[B] //Applicative



Monads can be built in different ways, Scala allows U to pickup different implementations that depends on conditions U r working now. Additionally methods can have a different names then the target of today be able to recognize monad method.

Let's start from the refactoring, as U can find our version of map method "smells".


    [1] def map[A, B](func: A => B): Box[A] => Box[B] =
                  (box: Box[A]) => new Box(func(box.value))


One of the problems: we can't get a profit from the implicit type detection from compiler and can't use syntax sugar of closures.

Let's rock & roll, simplifying map from returning a function:

    Func(A => B) : M[A] => M[B]

to returning a required value

    Func(A => B, M[A]) :  M[B]

Instead of returning a function we can return Function apply result. Changing parameters postions in signature:

    Func(M[A], A => B) :  M[B]

Finally adding currying:
 
    M[A] => (A => B) => M[B]

Talking in Scala via implementation:

    [2] def map[A, B](box: Box[A]): (A => B) => Box[B] = 
        (func: A => B) => new Box(func(box.value))

Usage example:

    val mapFunc = Box.map[String, Int](new Box("12345"));
    val res = mapFunc(_.size) // Box[Int](5)

Or

    val box = Box.map(new Box("1")) { _.size} // Box[Int](1)


We got quite easy to use approach that is following the best practices and usually described as example in Monad articles/books.
Lets drive into Scala - applying OOP to Monad. We started from container M (Box). It is a class, state-full and immutable.
Returning to last currying function:

        M[A] => (A => B) => M[B]

While we are starting from the M[A], we already have a state (type of M and value of A).
Then rephrasing

    M[A] has a method (A => B) => M[B]

Talking in Scala:

    [3] class Box[A](val value: A) {
            def map[B](func: A => B) = new Box(func(value))
        }
 
And simple way of using:

    val box = new Box("1") map ( (str:String) => str.charAt(0) )

or

    val box = new Box("1") map ( _.charAt(0) )

This code shines and looks as the best one way of doing Monads in Scala. While Scala supports OOP it was decided that minimum incapsulating container is the class. Later we will know that Monad, Functor, Filter and other are connected and should be presented in one wrapper class.
Real life conditions can make it impossible to implement, don't forget we are using Object Oriented approach than we can add behavior to class only if we can edit the source - that isn't always the case. Later I will show workaround example.
Scala has an embedded support for Monad via for-yield construction. In Scala for like a map

    [4] for (x <- expryield resultExpr  
equals to
    expr map {x =resultExpr}
As an example we will reuse Box implementation from the previous convention [3], and put it into the for-yield construction, at the end we are just doing doubling for integer:
    val box = new Box(1)
    val res = forb <- box } yield b * 2 // Box[Int](2)
Quite easy ha? In next session we will look how it works as a flatMap as well.
We will review map in details in lesson 5.
For map should be enough, today U've seen many faces of map function and can recognize it in the code. Don't be scared with implicit wrapper for container class that can add Monadic functionality, we will review in next session this example.







   

No comments:

Post a Comment