I originally wrote a long introduction to this article about how to work with the Scala Option/Some/None classes, but I decided to keep that introduction for a future article. For this article I’ll just say:
- idiomatic Scala code involves never using null values
- because you never use nulls, it’s important for you to become an expert at using
Option
,Some
, andNone
- initially you may want to use match expressions to handle
Option
values - as you become more proficient with Scala and Options, you’ll find that match expressions tend to be verbose
- becoming proficient with higher-order functions (HOFs) like
map
,filter
,fold
, and many others are the cure for that verbosity
Given that background, the purpose of this article is to show how to use HOFs rather than match expressions when working with Option
values.
Sample data
Here are some simple functions and values that will be used in the table that follows:
def p(i: Int): Boolean = i == 0 // A => Boolean def f(i: Int): Int = i * 2 // A => A def fo(i: Int): Option[Int] = Some(i * 2) // A => Option[A]
val option: Option[Int] = Some(1) val none: Option[Int] = None
val default = 1 val defaultSome = Some(1) val stringOption = Option("foo")
In that code, p is a predicate (of type Int => Boolean
), f
is a simple function of type Int => Int
(or more generally, A => A
), fo
is a function that returns an Option (A => Option[A])
, and hopefully the rest of the code makes sense.
Notes about the following examples:
- Because you know what result type you need when working with an Option in a specific situation, the table is sorted by the expression return type, which is shown in the first column
- While my code uses
Int
values, in all but one example you can think of the expressions as using a generic typeA
From match expressions to higher-order functions
Here are the examples, grouped by the type the expressions return:
Result Type |
Match Expression | HOF |
---|---|---|
Unit |
option match { |
option.foreach(println) |
A |
option match { |
option.getOrElse(default) |
A |
option match { |
option.map(f).getOrElse(default) |
Option[A] |
option match { |
option.map(f) |
Option[A] |
option match { |
option.orElse(defaultSome) |
Option[A] |
option match { |
option.filter(p) |
Option[A] |
option match { |
option.filterNot(p) |
Boolean |
option match { |
option.forall(p) |
Boolean |
option match { |
option.exists(p) |
Boolean |
option match { |
option.isEmpty |
Boolean |
option match { |
option.isDefined |
Boolean |
option match { |
option.contains(1) |
Int |
option match { |
option.size |
Int |
option match { |
option.count(p) |
Seq , List , etc. |
option match { |
option.toSeq |
Either[Int,Int] |
option match { |
option.toRight(default) |
Either[Int,Int] |
option match { |
option.toLeft(default) |
A or null |
stringOption match { |
stringOption.orNull (only use this for Java APIs that need it) |
Notes
A few notes:
- Note that in the
fold
example its use is fairly consistent with sequences, where a fold takes an initial seed value and a folding function (see my Scala fold and reduce tutorial for more details on those functions) - I put the
null
example in the last row because you should never use that, unless you’re interacting with a Java API that needs it - In the future I’ll include a companion article that explains and organizes these examples in a different way
Resources
I initially started writing this article based on my own experience, and then was further propelled to investigate it after I saw this tweet by Jon Pretty about using fold instead of match. That led me to this Tony Morris article about HOFs and Option, and most recently I found this video by Marconi Lanna, which helped to round out the last few examples in the table.