Tuesday, March 11, 2008

Maybe Scala

Working with languages that grow organically with your programs is real fun. You can simply visualize the whole program as a living organism evolving dynamically in front of you. Just fire up your REPL and see for yourself how the malleable syntactic structures of the language grow in front of your eyes, alongside your program. Whether this is through Lisp macros or Ruby meta-programming or Scala control structures, the secret sauce is in the ability to implement more and more powerful abstractions within the language. But what makes one language shine more compared to another is the ability to combine abstractions leading to more powerful syntactic structures.

Recently people have been talking about the Maybe monad and its myriads of implementation possibilities in Ruby. Because of its dynamic nature and powerful meta-programming facilities, Ruby allows you to write this ..


@phone = Location.find(:first, ...elided... ).andand.phone



Here andand is an abstraction of the Maybe monad that you can seamlessly compose with core Ruby syntax structures, effectively growing the Ruby language. Now, compare this with the Null Object design pattern in Java and C++, which is typically used in those languages to write null safe code. Abstractions like Null Object pattern encapsulate null handling logic, but do not relieve programmers from writing repetitive code structures - you need to have a Null Object defined for each of your abstractions!

I have been working with Scala in recent times and really enjoying every bit of it. Professionally I belong to the Java/JVM community, and the very fact that Scala is a language for the JVM has made it easier for me. After working for a long time in enterprise projects, I have also been affiliated to the statically typed languages. I always believed that static encoding of type information on your values serve as the first level of unit testing, constraining your data to the bounds of the declared type, as early as the compile time. However, at times with Java, I also felt that complete static typing annotations adds to the visual complexity in reading a piece of code. Now working in Scala made me realize that static typing is not necessarily a cognitive burden in programming - a good type inferencing engine takes away most of the drudgery of explicit type annotations of your values.

Enough of Scala virtues, let's get going with the main topic .. Scala's Maybe monad, it's marriage to for-comprehensions and it's implicit extensibility ..

Scala offers the Option[T] datatype as its implementation of the monad. Suppose you have the following classes ..


case class Order(lineItem: Option[LineItem])
case class LineItem(product: Option[Product])
case class Product(name: String)



and you would like to chain accessors and find out the name of the Product corresponding to a LineItem of an Order. Without drudging through boilerplate null-checkers and Baton Carriers, Scala's Option[T] datatype and for-comprehensions offer a nice way of calling names ..


def productName(maybeOrder: Option[Order]): Option[String] = {
    for (val order <- maybeOrder;
         val lineItem <- order.lineItem;
         val product <- lineItem.product)
             yield product.name
}



The example demonstrates a very powerful abstraction in Scala, the for-comprehensions, which works literally on anything that implements map, flatMap and filter methods. This is also an illustration of how for-comprehensions in Scala blends beautifully with its implementation of the Maybe monad (Option[T]), leading to concise and powerful programming idioms. I think this is what Paul Graham calls orthogonal language in his On Lisp book.

David Pollack implements similar usage of the Maybe monad in his Lift framework. Here is a snippet from his own blog ..


def confirmDelete {
    (for (val id <- param("id"); // get the ID
        val user <- User.find(id)) // find the user
     yield {
         user.delete_!
         notice("User deleted")
         redirectTo("/simple/index.html")
     }) getOrElse {error("User not found"); redirectTo("/simple/index.html")}
}



The find method on the model class and the param method that extracts from the request, all return Option[T] - hence we do not need any explicit guard to check for null id or "user not found" checks.

In the first example, if the method productName() gets any null component in the path of accessing the name, it returns None (as it should be). What if I would like to know which part is None as part of the diagnostic message ?

Just pimp my Option !


class RichOption[T](value: Option[T]) {
    def ifNone(message: String) = {
        value.getOrElse(error(message))
    }
}



and add the implicit conversion ..


implicit def enrichOption[T](opt: Option[T]): RichOption[T]
    = new RichOption(opt)



Now the method becomes ..


def productName(maybeOrder: Option[Order]): Option[String] = {
    try {
        val nm = maybeOrder.ifNone("null order")
                           .lineItem.ifNone("null line item")
                           .product.ifNone("null product")
                           .name
        if (nm == null)
            None
        else
            Some(nm)

    } catch {
        case e => println(e.toString()); None
    }
}



and the method invocation reports with meaningful messages in case of null accesses.

8 comments:

rickynils said...

Just a note on the for-syntax, you don't need to use 'val', and if you use braces instead of parantheses you can skip the semi-colons:

def productName(maybeOrder: Option[Order]): Option[String] =
for {
order <- maybeOrder
lineItem <- order.lineItem
product <- lineItem.product
} yield product.name

Xanana Gusmao said...

Debasish,

Is it correct to say that the Option class is a glorified implementation of the Null Object pattern ?

Thanks

X

Debasish said...

@rickynils:
Thanks .. I am only learning Scala.

@xanana:
reminds me of the generalization of Greenspun's 10th rule, once again .. "Any sufficiently complicated platform contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of a functional programming language."

As I mentioned in the post, Null Object pattern has the same objective, and when implemented in a language like Java or C++ leads to lots of boilerplates and repetitive code. OTOH, monads are true abstractions that can be composed together with your core language leading to more powerful abstractions.

villane said...

I would also write the for as rickynils suggested. Another option is to write the function like this (i'm not sure if I prefer it though):

def productName(maybeOrder: Option[Order]) =
maybeOrder flatMap (_.lineItem) flatMap (_.product) map (_.name)

This should be pretty close to what the for {} expression is translated to by the compiler. But I think it's somewhat difficult to remember where to use 'map' and where 'flatMap' (flatMap "flattens" the options, but several maps would result in something like Option[Option[Option[String]]] )

Xanana Gusmao said...

@Debasish,

The Option class seems to be more verbose than the Option type feature found in Nice.

Debasish said...

@xanana:

Sure, Nice has nullable types (?Type), which are more concise. However, Option[T] in Scala being a monad (and implemented as a case class) can be composed more easily. You can manipulate it's behavior (as I have shown, using implicits) and make it more malleable. Also, Groovy's safe operator order?.lineitem?.product?.name is a good concise implementation of the same concept.

Debasish said...

@villane:
Yeah .. for-comprehensions are a syntactic sugar for the flatmap/map combinations that u mention. I prefer to use the for-comprehensions though. It is nice and intuitive, having less accidental complexity and more intention-revealing.

Antony Stubbs said...

+1 for Groovy's safe operator. Would be nice if scala would have something similar. The Scala option system is indeed more powerful / felxible, but is overkill and verbose for the ~90% use case.