Scala: Handling nested Options with flatMap and for

Summary: In this article I show two ways to extract information from nested optional fields in your Scala domain models. This example is a little contrived, but if you have a situation where one Option instance contains one or more other Options, this article may be helpful.

Back to top

Optional fields in your domain model

There are times when you’re creating your domain model when it makes sense to use optional fields in your case classes. For instance, when you model an Address, the “second street address” isn’t needed for all people, so making it an optional field makes sense:

case class Address(
    street1: String,
    street2: Option[String],
    city: String,
    state: String,
    zip: String
)

True confession: When I first started working with Scala I used to hate using Option fields in case classes. But once I (a) swore off using null values, and (b) learned how to properly work with Options, I saw the beauty of the approach. For instance, one look at this class definition tells you that there may or may not be a street2 field; as the Option wrapper type implies, its optional.

Taking this a step further, it’s also possible that when you create a new Person instance in an application, you might not know the person’s address, so that field may also be optional:

case class Person(
    name : String, 
    address : Option[Address]
)

So now Address has the optional field street2, and Person has the optional field address.

Back to top

When Options contain other Options (nested Options)

Now imagine a case where you have an Option[Person] in an application. For instance, maybe you’re getting a list of all of the salespeople for a region, and because some positions may not be filled, that query returns a list of Option[Person], i.e., a List[Option[Person]]. Next, further assume that you’re working on a piece of code that needs to get the street2 portion of each person’s address. How would you do this?

When you start writing this function, you know that you’ll be given an Option[Person] as input, so you can begin to sketch the function signature like this:

def getStreet2(maybePerson: Option[Person]) ...

Next, you know that street2 is optional — an Option[String] — so you can add the function’s return type:

def getStreet2(maybePerson: Option[Person]): Option[String] = ...

Now all you have to do is to fill out the body of the function to match that signature.

Back to top

Using flatMap

One way to handle Options that contains other Options is to use flatMap. This solution shows how to flatMap each Option to get the result you want:

def getStreet2(maybePerson: Option[Person]): Option[String] = {
    maybePerson flatMap { person =>
        person.address flatMap { address =>
           address.street2
        }
    }
}

While most people prefer for expressions — more on that shortly — this is a common use of flatMap. Depending on your needs, you can use a similar approach with any monad, i.e., Try, Either, List, Future, etc.

Here’s a quick explanation of the getStreet2 code:

  • maybePerson is an Option, so you flatMap it to get a Person instance
  • Because address in Person is also an Option, you flatMap it to get an Address instance
  • Once you have the address, you yield address.street2

As I wrote in Learning Functional Programming in Scala, when you write code like this, all you have to do is focus on the “happy path,” i.e., the success case. In this algorithm, the success case means that all of those Options will return Some instances, and you’ll get the street2 field in the end. As I also mention in the book, the “unhappy path” takes care of itself.

Back to top

Some sample data

A couple of examples will show how getStreet2 works. First, in this example, I use None for the street2 field, so getStreet2 yields a None:

scala> val a1 = Address("123 Main Street", None, "Talkeetna", "Alaska", "99676")
a1: Address = Address(123 Main Street,None,Talkeetna,Alaska,99676)

scala> val p = Person("Al", Some(a1))
p: Person = Person(Al,Some(Address(123 Main Street,None,Talkeetna,Alaska,99676)))

scala> getStreet2(Some(p))
res0: Option[String] = None

Second, in this example I do have a street2 address (“Apt. 1”), so getStreet2 yields a Some:

scala> val a2 = Address("123 Main Street", Some("Apt. 1"), "Talkeetna", "Alaska", "99676")
a2: Address = Address(123 Main Street,Some(Apt. 1),Talkeetna,Alaska,99676)

scala> val p = Person("Al", Some(a2))
p: Person = Person(Al,Some(Address(123 Main Street,Some(Apt. 1),Talkeetna,Alaska,99676)))

scala> getStreet2(Some(p))
res0: Option[String] = Some(Apt. 1)

This shows that getStreet2 works as desired.

But if you don’t like flatMap ...

Back to top

Most humans like for expressions

While my brain is at a point where the flatMap code now makes sense and is readable, most people prefer for expressions to flatMap, so I suspect that they’d write getStreet2 like this:

def getStreet2(maybePerson: Option[Person]): Option[String] = {
    for {
        person  <- maybePerson
        address <- person.address
        street2 <- address.street2
    } yield street2
}

If you take the time to use my sample data with that function in the REPL, you’ll see that it works just like the flatMap version of getStreet2.

Back to top

Motivations

My main reason for writing this article was to demonstrate two ways to deal with Option instances that contain other Options. As shown, you can use (a) flatMap or (b) a for expression to traverse the Options to get the value you want.

A second reason for writing this article was to give you a little more exposure to flatMap. When I first learned Scala, I always thought of flatMap in terms of collections classes, but once you get into other monadic data types like Option, Try, Either, etc., you realize that there’s a very different way to think about flatMap, as shown in this example.

For those who have read Learning Functional Programming in Scala, you might know that I have a cheeky grin on my face when I use the term, “monadic,” so here’s a ;) for you.

Back to top

See also

This article was inspired by an article titled, FP for the average Joe - II - ScalaZ Monad Transformers.

Back to top

Add new comment

The content of this field is kept private and will not be shown publicly.

Anonymous format

  • Allowed HTML tags: <em> <strong> <cite> <code> <ul type> <ol start type> <li> <pre>
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.