Scala: How to use higher-order functions (HOFs) with Option (instead of match expressions)

I originally wrote a long introduction to this tutorial 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, and None
  • 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 type A

From ‘match’ expressions to higher-order functions (HOFs)

Here are the examples, grouped by the type the expressions return:

Result
Type
Match Expression HOF
Unit option match {
  case Some(i) => println(i)
  case None => ()
}
option.foreach(println)
for (o <- option) println(o)
A option match {
  case Some(i) => i
  case None => default
}
option.getOrElse(default)
A option match {
  case Some(i) => f(i)
  case None => default
}
option.map(f).getOrElse(default)
option.fold(default)(f)
Option[A] option match {
  case Some(i) => fo(i)
  case None => None
}
option.map(f)
option.flatMap(i => fo(i))
for (i <- option) yield f(i)
Option[A] option match {
  case Some(x) => Some(x)
  case None => defaultSome
}
option.orElse(defaultSome)
Option[A] option match {
  case Some(x) if p(x) =>
    Some(x)
  case _ => None
}
option.filter(p)
option.find(p)
Option[A] option match {
  case Some(x) if !p(x) =>
    Some(x)
  case None => None
}
option.filterNot(p)
Boolean option match {
  case Some(x) => p(x)
  case None => true
}
option.forall(p)
Boolean option match {
  case Some(x) => p(x)
  case None => false
}
option.exists(p)
Boolean option match {
  case Some(a) => false
  case None => true
}
option.isEmpty
Boolean option match {
  case Some(x) => true
  case None => false
}
option.isDefined
option.nonEmpty
Boolean option match {
  case Some(x) => x == 1
  case None => false
}
option.contains(1)

(the example uses `x == 1`
because i use Option[Int])
Int option match {
  case Some(x) => 1
  case None => 0
}
option.size
Int option match {
  case Some(x) if p(x) => 1
  case _ => 0
}
option.count(p)
Seq, List, etc. option match {
  case Some(x) => Seq(x)
  case None => Nil
}
option.toSeq
option.toList

(also toVector, toArray,
toSet, etc.)
Either[Int,Int]
  = Right
option match {
  case Some(x) => Right(x)
  case None => Left(default)
}
option.toRight(default)
Either[Int,Int]
  = Left
option match {
  case Some(x) => Left(x)
  case None => Right(default)
}
option.toLeft(default)
A or null stringOption match {
  case Some(x) => x
  case None => null
}
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

UPDATE: For more information, see my free “Higher-order functions in Scala 3” training video.

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.