The last edits I made to the original 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: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |