How to process multiple Option values in a Scala ‘for’ loop

The last edits I made to the Scala Cookbook were in June, 2013, and after all this time there aren’t many things I wish I had added to the Cookbook. Yesterday I ran into one thing that I don’t think I included in the Cookbook: How to process multiple Option values in a Scala for loop (for comprehension). Here’s a quick look at how to do this.

For the impatient

For those who just want to see a for comprehension that processes multiple input Option values, here you go:

val lengthOption = convertNumericStringToInt(m.group(3).trim)  // Option[Int]
val timeUnitOption = durationMap.get(m.group(4).trim)          // Option[String]
for {
    i <- lengthOption
    tu <- timeUnitOption
} yield FiniteDuration(i, tu)  // an Option[FiniteDuration]

As you can see from my comments, this comprehension processes two input Option values and returns a type of Option[FiniteDuration].

I’m often amazed by how Scala works, and what’s great here is that this for comprehension returns an Option value. If you think about it, the comprehension could just return a FiniteDuration if the two input Option values contained Some values. But then what would it do if one input value was a Some and the other was a None, or if both input values were None? When you think about those possibilities, you realize that having the for comprehension return an Option is the correct solution.

Longer explanation

For my Sarah project, I now handle spoken phrases that can have variables in them. For instance, imagine that you want to be able to set a timer by voice. To do so, you’d say things like this to your computer:

“Set a timer for one hour, please.”

“Set a timer for 10 minutes.”

“Sarah, set a timer for two hours.”

As you can see, these sentences are all a little different, which is what I mean when I say that they can have variables.

Looking at the first two sentences, the parts I’m interested in are the “one hour” and “10 minutes” areas. For my purposes, I want to parse these sentences and do the following:

  • Convert the “one” and “10” strings into a variable I name lengthOption
  • Convert the “hour” and “minutes” strings into a variable I name timeUnitOption

I parse the spoken text using regular expressions, and the resulting code I use to create lengthOption and timeUnitOption looks like this:

val lengthOption = convertNumericStringToInt(m.group(3).trim)  // Option[Int]
val timeUnitOption = durationMap.get(m.group(4).trim)          // Option[String]

Now that I have those two Option values, I can write my for loop.

The for-comprehension

For my purposes I wrote this for comprehension to process my two Option values:

for {
    i <- lengthOption
    tu <- timeUnitOption
} yield FiniteDuration(i, tu)  // Option[FiniteDuration]

As you can see from the comment, this comprehension yields an Option[FiniteDuration]. I use this comprehension to yield this Option from one method, and consume it in another method. In that second method I call the value durationOption, and handle it like this:

durationOption match {
    case Some(duration) =>
        // this first line creates a "timer"
        actorSystem.scheduler.scheduleOnce(duration, brain, PleaseSay("This is a timer reminder."))
        brain ! PleaseSay("The timer has been set.")
        true
    case None =>
        // it matched our pattern, but we couldn't extract the intValue and timeUnit for some reason
        brain ! PleaseSay("Sorry, I couldn't understand that.")
        true
}

The way this works is that if durationOption is a Some value, I create a timer and then let the user know that I created the timer. (Sending the PleaseSay message to the brain results in that text being sent to Sarah’s mouth, and therefore being spoken by Sarah.) On the other hand, if durationOption is a None value, I just tell the user that I couldn’t understand what was spoken, and a timer is not set.

The complete for-comprehension method

For the purpose of completeness, here’s the complete Scala source code for the method that contains my for comprehension:

// get (a) the numerical time and (b) seconds/minutes/hours from the spoken phrase,
// and create a scala.concurrent.duration.Duration from it
def getDurationFromSpokenPhrase(spokenPhrase: String): Option[FiniteDuration] = {
    val m = compiledPattern.matcher(spokenPhrase)
    if (m.find) {
        val lengthOption = convertNumericStringToInt(m.group(3).trim)
        val timeUnitOption = durationMap.get(m.group(4).trim)
        for {
            i <- lengthOption
            tu <- timeUnitOption
        } yield FiniteDuration(i, tu)  // yields as an option
    } else {
        None
    }
}

If you’re interested, you can find the complete source code for the class that contains this method here on Github:

You can also find the complete source code for the class that contains the durationOption code here:

If you wanted to see an example of using multiple Option values in a Scala for loop/comprehension, I hope this has been helpful.

... this post is sponsored by my books ...