Scala Options the slick way

Urs Peter

The Scala Option type is key for dealing with variables that can have values or not. Most libraries and applications make use of this handy type. However, it's usage in certain cases can lead to rather verbose code. This blog explains how to deal with this particular case in an elegant way using implicits. Read on to see how easy it is to tailor any kind of existing Scala type to perfectly fit your needs based on an example with Options.

I love the Option type in Scala. Options provide a very concise way to express whether a variable can have a value or not. Whereas a programmer can express his/her intent in Scala in an extremely concise and compact way I must admit that this is not always true when dealing with Options.
Depending whether I'm dealing with a Some() a want to convert the value of type X to a value of another type Y. Otherwise, in case it's a None, I want to provide a default value of the target type Y. When writing Scala code I find myself using this construct quite often. To illustrate this let’s consider the following snippet:

val myOpt:Option[Int] = Some(1234)
val converted = myOpt match {
case Some(v) => v.toString.reverse.mkString("-")
case None => "unknown"
}
assert("4-3-2-1" == converted)

Basically, what we want to achieve here is a conversion from Int to a String depending whether the Option is of type Some(Int). Otherwise we want to provide a default value of “unkown”.

This is the only case I can think of where I can associate Scala with boilerplate code. Four lines of code to perform a simple conversion or provide a default value depending whether we are dealing with a Some or a None is quite a lot. The good news is, that there is an alternative. By using other methods of the Option API I could convert this into a one-liner as follows:


val myOpt:Option[Int] = Some(1234)
val converted = if(myOpt.isDefined) myOpt.get.toString.reverse.mkString("-") else "unknown"
assert("4-3-2-1" == converted)

Apparently, it’s not that hard to put the desired conversion in one line of code, however, using conditional logic to achieve the desired result tempers my enthusiasm considerably. What I actually want is the following:


val myOpt:Option[Int] = Some(1234)
val converted = myOpt some_? (_.toString.reverse.mkString("-")) orNone "unknown"
assert("4-3-2-1" == converted)

Personally, I would find such an API for Option very intuitive to use, concise, compact and fully OO. So, since it’s not there (yet) we have to come up with a solution ourselves. Since we want to add new functionality to an existing type without being able to modify the source code, the way to go are implicits. A possible solution looks as follows:

implicit def fromOptionToConvertedVal[T](o:Option[T]) = new {
def some_?[R] (doWithSomeVal:(T) => R) = new {
def orNone(handleNone: => R) = o match {
case Some(value) => doWithSomeVal(value)
case None => handleNone
}
}
}

What is happening here? We first define an implicit function, which takes an Option as parameter. The conversion returns an anonymous object, which contains one function with the name some_?. Any name would do of course. The some_? function is a higher order function that takes a function parameter of type T, which is the same as the one of the Option. The function parameter of some_? then returns a new type of R, since we want to convert the initial value of type T to R.

Instead of doing something within the function body, we return another anonymous object with the counterpart of some_? called orNone. The orNone function takes a call-by-name argument. The body of the orNone does the actual work. Based on whether the Option is a Some the function parameter of some_? is executed otherwise the call-by-name argument of the orNone, which leads to the desired result.

Bottom line: if you are missing something in any library you use in Scala, implicits provide you the perfect means to add everything you like so that it works for you. That is the real ‘option’ Scala offers you ;-)

Comments (7)

  1. Will Sargent - Reply

    June 2, 2011 at 2:44 am

    You could also use:

    myOpt.map { _.toString.reverse.mkString("-") }.getOrElse("unknown")

    • Urs Peter - Reply

      June 2, 2011 at 12:33 pm

      Cool, that's concise, compact, fully OO and - even better - according to the api. My enthusiasm with fiddling with implicits must have blurred my vision a little ;-).

  2. Raymond - Reply

    June 2, 2011 at 3:29 pm

    Hi Urs,
    nice post.
    we used the map orElse also in the workshop, which is a good solution, although the orElse is not a good thing for performance (when it counts). Another option (no pun intended) is the scalaz Option 'pimp'

  3. Will Sargent - Reply

    June 3, 2011 at 4:57 am

    To be fair, I got most of what I know about Option from Tony Morris's cheat sheet.

    http://blog.tmorris.net/scalaoption-cheat-sheet/

    It's an excellent resource when you want to avoid "null infection" in your code.

  4. Vassil Dichev - Reply

    June 4, 2011 at 1:13 pm

    Like Will has said, you can really rename "some_?" to "map" and "orNone" to "getOrElse".

    Otherwise what you want is called a catamorphism, implemented in Scalaz as cata/fold:

    http://stackoverflow.com/questions/5328007/why-doesnt-option-have-a-fold-method

    and to continue the learning experience from Tony Morris's blog, here's some excellent catamorphism exercise:

    http://blog.tmorris.net/debut-with-a-catamorphism/

    Isn't it interesting when it turns out that the patterns you discover have a name? :)

  5. Tony Morris - Reply

    June 6, 2011 at 8:17 am

    Either has this method on its left/right projections. It is called "fold." List also has it -- it is called "foldRight." It is missing on Option but can be made of map+getOrElse. I would propose it be called fold, however, note that the combination of map+getOrElse is equivalent to the scala.Option structure. In other words, the following interface is isomorphic (same form) to scala.Option:

    trait MyOption[A] {
    def fold[X](some: A => X, none: => X): X
    }

    Therefore, you can write all the Option methods on this data type -- try it.

  6. Tony Morris - Reply

    June 6, 2011 at 8:28 am

    By the way, Scalaz includes these methods. Option.fold and option ? some(_) | none.

Add a Comment