Scala Saturday – The Option Type, Part 2

Last week we introduced the Option type as a way of representing when a function may or may not return a value.

A Collection of One

Another way to think about an Option is as a collection that contains no more than one element. Consequently, Option provides some collection-like semantics, e.g., fold, map, exists, and filter. This allows us to chain operations together without having to check at each step whether the value is Some or None. We can put that off until the end of the sequence of operations and only do the check once. That way, the algorithm is more readable; it’s not cluttered with a bunch of if-else noise.

Say that you have a User type:

case class User(id: String, name: String)

Let’s say that your system is a website. In the top, right corner of the site, you want to display the name of the user who is currently logged in. You need a function that returns the user currently signed in:

val authenticatedUser: () => Option[User] = // ...

Now why would this function return an Option? Well, the user browsing your site may not have logged in at this point. If that’s the case, then the current user is None. So then, the code to get the name of the current user is this:

val nameOpt = authenticatedUser().map(
// nameOpt: Option[String]

Notice how the type of nameOpt is Option[String]. In other words, assuming that authenticatedUser() returns a Some[User], Scala knows that the property is a String. Nevertheless it propagates the uncertainty, if you will, along through the map operation. Any operations you do on an Option only happen if the Option is a Some. Otherwise, Scala happily ignores the operation and continues to propagate the None.

OK, so now that we have a final result Option, what do we do with it? That’s where Option.getOrElse() comes in:

val name = nameOpt getOrElse "Guest"
// name: String

If nameOpt is a Some, name is the value in the Some. If nameOpt is a None, then name is "Guest". You can test both cases in the Scala REPL. To see it work in the case of a Some:

val authenticatedUser: () => Option[User]  =
    () => Some(User("bcollins", "Brad Collins"))
val name = authenticatedUser()
             .map(         // Some("Brad Collins")
             .getOrElse("Guest")  // "Brad Collins"
// name: String = Brad Collins

And then to see it with a None:

val authenticatedUser: () => Option[User]  =
    () => None
val name = authenticatedUser()
             .map(         // None
             .getOrElse("Guest")  // "Guest"
// name: String = Guest

Unraveling the Options

Remember how the collections API defines the flatMap method? For instance, this is the List.flatMap() method:

// signature slightly modified for readability
def flatMap[B](f: (A) ⇒ List[B]): List[B]

It’s like map, but flatMap takes a transformation function that takes an element and returns a list of values rather than just a single value. Then instead of returning a list of lists, flatMap flattens them into a single list.

Option also has a flatMap method. It takes a function that returns an Option and, instead of returning a nested Option (i.e., an Option[Option[B]]), it just returns an Option[B]:

def flatMap[B](f: (A) ⇒ Option[B]): Option[B]

Maybe instead of having an authenticatedUser() function, your app just has a session property. To get the name of the current user, you have to walk the property tree from the application to the session to the user and then finally the name. Application.session is an Option to indicate that there may be no valid session. Session.user is an Option to indicate that there may be no user signed in. Finally, maybe we don’t even force a user to have a name, just a user ID:

case class User(id: String, name: Option[String] = None)
case class Session(user: Option[User] = None)
case class Application(session: Option[Session] = None)

You walk the property tree like this:

val nameOpt = app.session
val name = nameOpt getOrElse "Guest"

Now if any property is None, from session to user to name, nameOpt is None, and name is "Guest".

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.