Monads - Functional IoC pattern for Scala

This post is not yet another introduction to Monads. In fact, I hesitated whether to use the term in the title. Eventually I did, so I wouldn’t get comments that the article is in fact about Monads. It is, but not for the reasons most articles are about.

 

I want to discuss the design pattern that they offer. That is inversion of control between values and functions. Usually we think as values being the arguments to functions. But if we switch things around, making functions the argument of values, it can help make some code more concise and clear. It helps to model different code flows than foo(bar(car()))

 

Since I mentioned the word Monad, I need to give some kind of introduction. It is very superficial. I will then follow with some examples.

 

The article tries to reach those not very familiar with Scala, but I will use Scala syntax.

 

Explanation of Monads (I know, there are hundreds like this…)

Monads can be thought of as a design pattern that allows chaining of functions/methods in non trivial way. In Scala, some constructs offer some syntactic sugar to work with them

 

Essentially, a monad is a “box”. “Monadic” functions (or methods) put their result in the box and return the box.  Other functions that require the value are passed to the monad, which extracts the value and calls the functionBy being in charge of calling the function the monad can add special behavior.

 

There are some mathematical rules, to what is strictly considered a monad, but I won’t dwell on them here. Google is your friend…

For the purpose of this article, a monad is a class M which looks like:

 

class M[T](t: T) {
   def flatMap[U](f: T => M[U]): M[U]
}

 

  • A constructor that wraps the value
  • A method that accepts functions that work on the value type, returning another monad
    • Note: the reason for the name flatMap is that this is the idiomatic Scala method. Using it Scala provides some syntactic sugar
    • Of course it is easy to add an operator (e.g., >>= from Haskell) and use implicit conversions so that it is used instead of flatMap

 

Here are some examples:

Optional Values

This first example is the one most familiar to Scala programmers. I present it here mainly as introductory to new comers. So it is both trivial and verbose.

 

What do you do when a method needs to return an integer value, but sometime there is none? (For example, Map#get where values are integers and the key does not exist). People may opt to return an “unreasonable” value, such as Int.MaxValue, or return an Integer object and ‘null’ when there is no value. Both of these approaches have two flaws:

 

The first (and least important for this discussion) is that it is not clear from the method declaration that you need to check on those special value. All you see is:

def foo : Int or def foo: Integer

 

The second is that in many cases we want to chain several methods, each can return “no value”. E.g., A student graduates if universityRegistry has student “john” and he has taken a course “required” and has gotten a grade and the grade is above 60. Anything else returns false. With returning null, the code looks like this

val student = studentRegistry .getStudent(studentName)

if (student == null) return false

val course = student.getCourse(courseName)

if (course == null) return false

val grade = course.grade

if (grade == null) return false

return grade > 60

 

This is verbose and the style in general is error prone.

 

Instead, use the Option[T] trait. It has two concrete implementations: Some[T] and None. Some carries an actual value and None has no value. A method definition is now:

def getStudent(name: String) : Option[Student]

The definition of flatMap is:

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

Explanation: If the option is a Some, then it applies the function to the boxed value. For None, it returns None (the function is not called)

 

Now convert all methods to return an Option:

StudentRegistry: def get(studentName: String): Option[Student]

Student: def getCourse(courseName: String): Option[Course]

Course: def grade: Option[Int]

And use:  

studentRegistry.getStudent(studentName) flatMap

_.getCourse(courseName) flatMap

_.grade flatMap 

{g => Some(g > 60)}

getOrElse false

 

So studentRegistry#getStudent returns an Option, and the call to flatMap passes the function {x => x.getCourse}  this produces an Option and it’s flatMap is called with _.grade. The final function returns always a Some[Boolean]. The final line unboxes the monad, returning its value if it is a Some and false otherwise.

 

Maybe it is tricky to understand this code, if not already familiar with Scala, but it is shorter, and if either method returns None, the whole evaluation is short circuited to return false.

 

Another advantage is that it is now clear from looking at the method signatures that they can return None.

 

Future Values

What if you have a set of methods that compute a value asynchronously? You want to call each one when the previous has calculated its value. The code might look like:

val future1 = o.foo(…)

val val1 = future1.get

val future2 = val1.bar(…)

val val2 = future2.get

 

Of course this is both verbose but also locks the thread each time #get is called.

 

Instead, we can introduce a Future[T] monad. Chaining the functions means that the monad calls the function once its value has been set. Here is the code:

class Future[A] {

   self =>

   def flatMap[B](f: A => Future[B]): Future[B] = new Future[B] {

     override def atEnd(g: B => Unit) = self.atEnd{v => f(v).atEnd(g)}

  }

  def atEnd(f: A => Unit) = synchronized {
    if (value.isDefined) f(value.get) else fvalue = f
  }

  def set(v: A) =  synchronized {
    value = Some(v)
    if (fvalue != null) fvalue(v)
  }

  var value: Option[A] = None

  var fvalue: A => Unit = null

}

 

(the implementation is rather trivial, to not get bogged down with details).

 

atEnd is a method to get us out of the monad. It accepts a function that will evaluate the value when it is ready. E.g., future.atEnd{v => println(“the value is: “ + v)}

 

flatMap accepts a function but doesn’t call it immediately. Instead, it creates a future whose atEnd will call the function. This future can now be used to chain more functions.  (note that if using flatMap, you can’t use atEnd and vice versa. Either you’re in the monadic context or not)

 

The calls now look:

o.foo flatMap _.bar flatMap car(_)  atEnd println

This will call foo, calling bar method of the result when it is ready and passing the result when it is ready to the car method. Finally, to get out of the monadic context, atEnd is used to print the result

 

Here’s an example:

scala> def thread(body: => Unit) = {val t = new Thread(new Runnable{def run = body}); t.start}

thread: (body: => Unit)Unit

 

scala> def f(i: Int) = {val fu = new Future[Int]; thread {Thread.sleep(2000); fu.set(i * 2)}; fu}

f: (i: Int)Future[Int]

 

scala> f(2) flatMap f atEnd println

8

 

Returning Many Results

Say you have functions that return several possible values. For example, the next possible moves in a game.  Chaining such functions means that for each value one returns, the next function should be evaluated and so on, combining all values at the end.

 

It just so happens that the Scala List collection can be used for this purpose. Here is how a method is defined:

def f(i: Int): List[Int]

and here is how to chain them

f flatMap g flatMap h

 

 

Queries

Returning to the student registry example, assume all methods return their results by querying a database. We want to work in a transaction, so one way is to add an implicit parameter to all methods that encapsulates the underlying ORM.

 

An alternative is for those methods to return a Query[T] monad. It does not box the result of the query, but the query itself. E.g., the JPQL string. Then flatMap just accumulates the functions. The final Query object can then be used to execute the queries one by one.

 

The benefit is that now the execution of the queries can be optimized, memorization of the functions can be used. And the programming style is more FP.

 

The implementation is similar to that of Future, so I won’t repeat it here.

 

 

I'd love to hear your thoughts in comments, or you can tweet me @ittayd

Thank you for your interest!

We will contact you as soon as possible.

Send us a message

Oops, something went wrong
Please try again or contact us by email at info@tikalk.com