asoft
sea

~ tiny vessels of code slowly floating out to sea

Powered by a motorboat called the S.S.Tumblr

3 years ago

@2:41am

2 notes
Comments

please yield to passing monads

One of the most common functional programming topics tossed about in the Scala community are monads. Even if you may not know what they are, chances are, you probably use them all the time.

Though there are already some decent postings on monads in Scala, I’d like to take a time out and describe how they are used in the Scala you probably use every day.

Raise your hand if you’ve ever pattern matched on a value that returned Some or None in Scala. Now raise your hand if you’ve ever used a Map in scala. If you raised your hand at least once you already use the most common monad in scala, Option. So what does it mean to be a monad?

That’s Monad not Nomad

Monads enable you to encapsulate the application of a function to a typed set of arguments. In other words, function bindings. Scala provides a few simple idiomatic tools do so: map and flatMap. You will find these implemented on many of the built in collections classes.

So why would you want to encapsulate a function’s application? Functions can have side effects. Functions can result in exceptional or illegal states of an application. Sometimes these exceptional states are declarative. With the support for higher-order functions in Scala, you can describe what something does without stating how. Thats where monads come in.

Back to our story. So. Option. What’s it to you? If you take a look at its source you see Option implements map and flatMap as

def map[B](f: A => B): Option[B] = 
  if (isEmpty) None else Some(f(this.get))

def flatMap[B](f: A => Option[B]): Option[B] = 
  if (isEmpty) None else f(this.get)

These are binding functions. If you are used to coming from most languages, you are probably used to writing defensive code in the form.

if(obj != null) {
   obj.play();
}

This is actually just a poor man’s abstraction for handling an erroneous state that is admittedly a a billion dollar mistake. Option is a monad that shields you from this messy situation and provides a higher level of abstraction, you either have Some value or None.

Are You or Are You Not Comprehending

That’s cool and all but how is that actually useful? Comprehensions my friend Watson.

Scala provides us with a pretty neat little control structure that might seem a bit strange in a world of inferior loops. I say inferior because most modern languages have control structures that allow you to just loop over collections of values without returning anything resulting in implicit side effects. I will call these for loops. In functional programming, it is idiomatic that everything returns a value. In Scala, I will call a for loop's analogy a for comprehension. I say comprehension because Scala is doing more that just naively looping over values. It is comprehending their arguments and applying functions by monadic means. This has much in common with Haskell’s do notation.

A refresher on for comprehensions.

In Scala, there are two usages of the for control structure.

First up is a for that returns Unit

for(i <- List(1, 2, 3)) { println(i) }

This, in its most basic form, looks outwardly like a loop control structure in other languages. In someways it is and in some ways it is not.

The main difference here is that the contents of for’s first argument can actually be stackable and contain logic.

for(i <- List(1, 2, 3); if(i > 1);
    j <- List(4, 5, 6)) { println(i * j) }

The second form is a for that returns a generated value.

for(i <- List(1, 2, 3)) yield { i * 2 }

This yields the value of List(2, 4, 6).

The generative form is the preferred form as you are returning the value of computation rather than performing a side effect like printing to standard output. This form also lends itself toward better testability and referential transparency.

This Ain’t your Mom’s Dad

So what’s do comprehensions have to do with Option?

There is some sleight of hand going on behind the scenes in a for comprehension.

Firstly, when you blur your eyes a bit you can see that for has the same appearance as a curried function.

foo( ... )( ... )

In Scala, parens are interchangeable with curly braces which can make a curried function look or feel more like a control structure.

for( ... ) { ... }

And if you really wanted

for { ... } ( ... )

But enough with emoticons.

You can actually apply any value as the first argument of a for comprehension given the following constraints.

For the Unit flavor of for comprehensions, your object must implement

def foreach[U](f: A => U): Unit

For the generative flavor, you must implement

def map[B](f: A => B): M[B]
def flatMap[B](f: A => M[B]): M[B] 

Where M is the type of Monad

Remember the currying I mentioned above? You can think the second half of the curry as the function f in map and foreach. The function that gets passed to flapMap is the result of the map operation at the inner most <-. This is the magic sauce that makes Scala’s for comprehensions chainable.

In Scala 2.8, you will also probably want to implement

def withFilter(f: A => Boolean)

WithFilter is used to support conditional logic within for loops.

Again, what’s all of this have to do with Options? Keep your bonnet on!

Guess what, Option just happens to implement all of these.

for(i <- Option(1)) { println(i) }

Prints 1

for(i <- Option(1); if(i > 1);
    j <- Option(4)) { println(i * j) }

Prints nothing (because the value of i is not greater than 1.

for(i <- Option(1)) yield { i * 2 }

yields Some(2)

And… Since these monadic for comprehending Options return Options you can append a default value at the end of a computation

(for(i <- Option(1); if(i>1)) yield { i * 2 }).orElse(Some(4))

yields Some(4)

Mmmm. Ok.

For Tickling Monads

Let’s take a closer look by implementing a for comprehending monad called Focomo that prints some output to show what in the tarnation for comprensions are doing with values. Sorry kids, for simplicity sake, Focomo will not do anything interesting based on what the the actual value is. We just want to see where for comprehensions like to tickle their arguments.

/** A for comprehension ready Monad */
case class Focomo[A](value: A) {
  self =>
  /** satisfies monadic binding of a function f that returns B */
  def map[B](f: A => B): Focomo[B] = { 
    println("map!"); 
    Focomo(f(value)) 
  }
  /** satisfies monadic binding of a function f that returns Focomo[B] */
  def flatMap[B](f: A => Focomo[B]): Focomo[B] = { 
    println("flatMap!"); 
    f(value) 
  }
  /** expect this to be called in a `Unit` for comprehension */
  def foreach[U](f: A => U): Unit = { 
    println("foreach!"); 
    f(value) 
  }
  /** for comprehension's `if` statements trigger this. 
   *  In a more useful monad, the result of the application 
   *  of a value to function f would determine the a special subclass
   *  of the monad or other either-or behavior.
   */
  def filter(f: A => Boolean): Focomo[A] = { 
    println("filter!"); 
    this
  }
  /** provides a delegate handler for calls to #withFilter */
  class WithFilter(p: A => Boolean) {
    println("with filter!")
    def map[B](f: A => B): Focomo[B] = self.filter(p).map(f)
    def flatMap[B](f: A => Focomo[B]): Focomo[B] = self.filter(p).flatMap(f)
    def foreach[U](f: A => U): Unit = self.filter(p).foreach(f)
    def withFilter(q: A => Boolean): WithFilter =
      new WithFilter(x => p(x) && q(x))
  }
  /** called with conditional statement in for comprehension */
  def withFilter(p: A => Boolean): WithFilter = new WithFilter(p)
}

Let’s play.

for { i <- Focomo(1) } { i }

Prints

foreach!


for { i <- Focomo(1); if(i < 2) } { i }

Prints

with filter!
filter!
foreach!

for { i <- Focomo(2) } yield { i  }

Prints

map!

And returns

Focomo(2)

for { i <- Focomo(2); j <- Focomo(3) } yield { i * j }

Prints

flatMap!
map!

And returns

Focomo(6)

And lastly,

for { i <- Focomo(2); if(i<4); j <- Focomo(3) } yield { i * j }

Prints

with filter!
filter!
flatMap!
map!

And Returns

Focomo(6)

So what did we get out of all of this? Well, not much from our little Focomo :). What I hope you did learn was the basic building blocks of how you can take advantage of the monadic hooks Scala provides in for comprehensions and how monads themselves are useful for encapsulating function bindings and managing common side effects.

  1. asoftsea posted this
blog comments powered by Disqus