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 |