From Get Programming with Scala by Daniela Sfregola

This article introduces the Option type and discuss why you would (or wouldn’t) want to use it and when.


Take 37% off Get Programming with Scala. Just enter fccsfregola into the discount code box at checkout at manning.com.


After reading this article, you’ll be able to:

  • Represent a nullable value using Option
  • Use pattern matching on instances of the type Option

After mastering high order functions, you’re going to learn about the type Option. In Scala, using null to represent nullable or missing values is an anti-pattern: use the type Option instead. The type Option ensures that you deal with both the presence and the absence of an element. Thanks to the Option type, you can make your system safer by avoiding nasty NullPointerExceptions at runtime. Your code will also be cleaner as you won’t need to preventively check for null values: you’ll be able to clearly mark nullable values and act accordingly only when effectively needed. The concept of an optional type isn’t exclusive to Scala: if you’re familiar with another language’s Option type, such as Java, you’ll recognize a few similarities between them. You’re going to learn about the structure of the Option type. In part 2, you’re going to learn how to create optional values and how to handle them using pattern matching. In part 3, you’re going to explore changing Option values with for-comprehension.

 

Consider this

Suppose you need to design a structure to represent nullable values: how would you indicate that an element in it may or may not be there?

Why Option?

Suppose you’ve defined the following function to calculate the square root of an integer:

  
 def sqrt(n: Int): Double =
     if (n >= 0) Math.sqrt(n) else null
  

This function has a fundamental problem. Its signature—what a function does—doesn’t  provide any information about its return value being nullable: you’ll need to look at its implementation—this is how a function computes a value—and remember to deal with a potentially null value. This approach is particularly prone to errors as you can easily forget to handle the null case, causing a NullPointerException at runtime, and it forces you to write a lot of defensive code to protect your code against null:

  
 val x: Int = ???
 val result = sqrt(x)
 if (result == null) {
          //protect from null here
 } else {
     // do things here
 }
  

The type Option is equivalent to a wrapper around your value to provide the essential information that it may be missing. Thanks to the use of Option, you no longer need to look at the specific implementation of a function to discover if its return value is nullable: this information is in its signature. The compiler also makes sure that you handle both the cases when it’s present and when it’s absent, making your application safer at runtime.

Creating an Option

After discussing how the use of Option can improve the quality of your code, let’s see how you can create instances for it. A nullable value either exists or it’s missing: the (simplified) definition of Scala’s Option shown in listing 1 reflects this structure.

Listing 1: The Option Type

  
 package scala
  
 sealed abstract class Option[A]
  
 case class Some[A](a: A) extends Option[A]
 case object None extends Option[Nothing]
  

Let’s analyze its definition line by line and see what each of them mean:

  • package scala

    The Option type lives in the scala package, and it’s already available into your scope without the need of an explicit import.

  • sealed abstract class Option[A]

    Option is an abstract class, and you can’t initialize it directly. It’s sealed: it has a well-defined set of possible implementations (i.e., Some of a given value and None). For the first time, you encountered the Scala notation for “generics,” which is Option[A]. An optional type works independently from the value it contains: you would expect an optional value to work in the same way of an optional integer, an optional string or any other optional value. With the annotation Option[A], you’re telling the compiler that you’ll associate Option with a type that you’ll provide as a parameter in initialization: Option has a “type parameter”. Scala has a convention of using upper case letters of the alphabet for type parameters, the reason why Option uses A, but you could provide any other name for it.

  • case class Some[A](a: A) extends Option[A]

    Some is case class and a valid implementation of Option that represents the presence of a value. It has a type parameter A that defines the type of the value it contains. Some[A] is an implementation of Option[A], which implies that Some[Int] is a valid implementation for Option[Int], but not for Option[String]:

      
     scala> val optInt: Option[Int] = Some(1)
     optInt: Option[Int] = Some(1)
      
     scala> val optString: Option[String] = Some(1)
     <console>:11: error: type mismatch;
      found   : Int(1)
      required: String
            val optString: Option[String] = Some(1)
      
    

    Scala’s type inference is about to infer type parameters. For this reason, you can write Some(1) instead of Some[Int](1).

  • case object None extends Option[Nothing]

    None is the other possible implementation for Option, and it represents the absence of a value. It’s a case object, which means it’s a serializable singleton object. The idea of a missing value is applicable to a value independently from its type: for this reason, None doesn’t have a type parameter, but it extends Option[Nothing]. Nothing has a special meaning in Scala: Nothing is the subclass of every other class; it’s the opposite of Any (see figure 1):


    Figure 1: In Scala, the types Any and Nothing have a special meaning. Any is the superclass of every other class—it’s the root of the class hierarchy. Nothing is at the bottom of the class hierarchy—it’s the subclass of every other class.


Because None extends Option[Nothing], you can use None as a valid implementation for Option independently of its type parameter. Thanks to its special meaning, Nothing will always be compatible with the type parameter you provided:

  
 scala> val optInt: Option[Int] = None
 optInt: Option[Int] = None
  
 scala> val optString: Option[String] = None
 optString: Option[String] = None
  

The terms None, Nothing and null can get confusing here; let’s recap what each of them means. None is an instance of the class Option, and it represents a missing nullable value. Nothing is a type that you can associate with all other Scala types. The term null is a keyword of the language to indicate a missing reference to an object.

 

Think in Scala: Class versus Type

In this article, I’ve used the terms class and type as synonymous for simplicity, but they are not the same in all cases.

A class represents a code element that has a particular behavior and that you can instantiate through its constructor. A type uniquely identifies a much broader category of items you can use in your programs. Let’s use the Scala REPL to see a few examples in action.

String has same class and type:

  
 scala> import scala.reflect.runtime.universe._
 import scala.reflect.runtime.universe._
  
 scala> classOf[String]
 res0: Class[String] = class java.lang.String
  
 scala> typeOf[String]
 res1: reflect.runtime.universe.Type = String
  

This isn’t the case when comparing Option[Int] with Option[String]: the both belong to the class Option but they have different types:

  
 scala> classOf[Option[Int]]
 res2: Class[Option[Int]] = class scala.Option
  
 scala> classOf[Option[Int]] == classOf[Option[String]]
 res3: Boolean = true
  
 scala> typeOf[Option[Int]]
 res4: reflect.runtime.universe.Type = scala.Option[Int]
  
 scala> typeOf[Option[String]]
 res5: reflect.runtime.universe.Type = scala.Option[String]
  
 scala> typeOf[Option[Int]] == typeOf[Option[String]]
 res6: Boolean = false
  

You can now re-implement your sqrt function to use the type Option as shown in listing 2:

Listing 2: The sqrt function with Option

  
 def sqrt(n: Int): Option[Double] =
   if (n >= 0) Some(Math.sqrt(n)) else None
  

Figure 2 provides a visual summary of Scala’s Option type.


Figure 2: Visual summary of the structure of the Scala’s Option type. An Option[A] is either a Some[A] of a value a or None, which is the representation for its absence.


Quick check 1

Write a function called filter which takes two parameters of type String, called text and word, and it returns an Option[String]. The function should return the original text if it contains the given word, otherwise it should return no value.

 

Convert Partial Functions to Total Functions returning Option

Partial functions can be a tool to abstract commonalities between functions. An example of a partial function is the following:

  
 val log: PartialFunction[Int, Double] =
     { case x if x > 0 => Math.log(x) }
  

When you call a partial function on an input which isn’t defined, it throws a MatchError exception, which isn’t ideal as exceptions are unpredictable. Instead of using a partial function, you can define a total function that returns an optional value: this approach protects your code from an unexpected MatchError exception at runtime. For example, you could re-implement the function log as a total function as follows:

  
 def log(x: Int): Option[Double] = x match {
   case x if x > 0 => Some(Math.log(x))
   case _ => None
 }
  

Unless you’re using an API or library which is explicitly requesting you to use partial functions, consider re-defining your partial functions as total functions to make your code safer at runtime by avoiding possible unpredicted exceptions.

Pattern Matching on Option

After looking at the structure of an Option, let’s see how you can handle it.

When pattern matching on a sealed item, the compiler warns you if you haven’t considered all its possible implementations, as this could cause a MatchError exception. You can pattern match on case classes and case objects. Let’s put everything together and see how you can pattern match and get an optional value.

When handling an optional value, one possibility is to use pattern matching to consider both the presence and the absence of a value:

Listing 3:  Pattern Matching over Option

  
 def sqrt(n: Int): Option[Double] =
   if (n >= 0) Some(Math.sqrt(n)) else None
  
  
 def sqrtOrZero(n: Int): Double =
   sqrt(n) match {               ❶ 
     case Some(result) => result 
     case None => 0              ❸ 
   }
  

❶   sqrt(n) returns a value with type Option[Double]

❷    if the value is present, return it

❸   if the value is missing, return 0

Note that pattern matching isn’t the only way to handle an optional value: in part 2, I’ll show you how you can achieve the same using predefined high order functions such as map and flatMap.

 

Quick check 2

Write a function called greetings that takes an optional custom message as its parameter and returns a string. When the optional parameter is defined (i.e. it contains a value) use it as greeting message, and when the optional parameter is missing use the predefined message “Greetings, Human!” For example, greetings(Some("Hello Scala")) should return the string “Hello Scala,” and greetings(None) should return “Greetings, Human!”

Summary

In this article my objective was to teach you about Scala’s Option type:

  • You discovered how you can use it to represent nullable values and how this can improve the quality of your code.
  • You learned how to create instances of Option and see how to handle them using pattern matching.

Let’s see if you got this!

 

Try this:

Define a case class Person to represent a person with a first name, an optional middle name, and a last name. Write a function that takes an instance of Person as its parameter, and returns a string describing its full name. For example, when representing a person whose first name is George, middle name is Watson, and last name is Lucas it should return the string “George Watson Lucas.” On the other hand, when representing a person whose first name is Martin, has no middle name, and last name is Odersky, it should return “Martin Odersky.”

Answers to Quick Checks

 

Quick check 1

A possible implementation for the function filter is the following:

  
 def filter(text: String, word: String): Option[String] =
   if (text.contains(word)) Some(text) else None
  

 

Quick check 2

You could implement the function greetings as follows:

  
 def greetings(customMessage: Option[String]): String =
   customMessage match {
     case Some(message) => message
     case None => "Greetings, Human!"
   }
  

That’s all for now. Stay tuned for part 2. If you’re interested in learning more about the book, check it out on liveBook here and see this slide deck.