Scala Tutorial: Using Options, Some, and None

The Option class is used to represent optional values in Scala. By representing values as either an instance of Some or None, Scala provides a more elegant way of handling null values. In this tutorial, we explore the basics of working with optional values in Scala. We'll explain the advantage of using options and provide some basic examples of implementing The Option, Some, None pattern.

What is an Option?

In Scala, the Option[T] class serves as a container for zero or one element of a given type T. If the element exists, it is an instance of Some[T]. If the element does not exist, it is an instance of None

val firstName: Options[String] = Some("Joe")

The above is an example of an optional string value.

Why Use Options?

Options provide a more elegant way of handling absent values in Scala. While traditional Java uses null to represent empty or missing values, this can lead to NullPointerException that break your code at runtime. By using Scala options, you are forced to explicitly address null or empty values at the type level.

Scala Option Basic Example:

The best way to understand optional values is by example:

case class Person(
  firstName: Option[String]
)

object OptionExample {
   def printName(name: Option[String]) = {
      name match {
        case Some(n) => println("Hello, my name is " + n)
        case None    => println("No name provided")
      }
   }

   def main(args: Array[String]) {
      val person = Person(Some("Joe"))
      val person2 = Person(None)
      printName(person.firstName) //output is "Hello, my name is Joe"
      printName(person2.firstName) //output is "No name provided"
   }
}

In the example above, notice how the Person defines a single firstName property of type Option[String]. Remember that the Option[T] serves as a container for zero or one element of a given type T. This means the firstName property is either an instance of Some(String) or None.

Why not just define the firstName as a String type? After all, the above example would still work if we defined firstName: String (and replaced None with null).

The reason why using an optional value is better is because we explicitly address the case for an absent value in our code. This helps us avoid NullPointerException at runtime. For example, the Scala Collections API uses optionals to avoid this issue:

val person = Map("name" -> "Joe", "age" -> 30)
person.get("name") // returns Some(Joe)
person.get("location") // returns None

Notice how calling get on a Scala Map returns an optional value. If we try to call get on a key that doesn't exist in the collection, it returns None and we won't get a NullPointerException at runtime.

Getting the Option Value

You'll notice we wrap optional values in Some(). To get the actual value stored, we have to unwrap the case class Some(). Below are some of the more popular methods for unwrapping optional values:

Pattern Matching:

Our first example implement pattern matching in the printName() method:

def printName(name: Option[String]) = {
   name match {
     case Some(n) => println("Hello, my name is " + n)
     case None    => println("No name provided")
   }
}

This approach is rather verbose however explicitly defines how to handle absent values.

Using getOrElse()

The getOrElse() method provides a cleaner way to unwrap optionals:

def printName(name: Option[String]) = {
   val n = name.getOrElse("No name provided")
   println(n)
}

Notice how the printName() function has been reworked to unwrap the name value using getOrElse(). If the name argument is an instance of Some(String), it will return the value. Otherwise, the provided argument "No name provided" will be used.

Using foreach()

Remember that the Option[T] class is a collection of zero or one elements of type T. This means we can use methods like foreach to extract optional values:

def printName(name: Option[String]) = {
  name.foreach{ n => println(s"Hello, my name is: $n")}
}

Notice how we call foreach() on the name argument. Please note that the function we pass to foreach will only execute if the name argument is of type Some(String). If we pass None into the printName() method then nothing will happen.

Checking if Option is None

You can easily check to see if an optional value is None through several methods:

val some:Option[String] = Some("some string")
val none:Option[String] = None
some.isDefined //returns true
none.isDefined //returns false
some.isEmpty //returns false
none.isEmpty //returns true

In this example, we use both the isDefined() method and isEmpty() to check if an optional value is None. Both methods return a Boolean value based on whether or not the value is an instance of Some or None.

Conclusion

Optional values are fundamental to Scala and functional programming. While Scala inherently uses optional values with Collections, you can explicitly handle absent values in your code using optional values. This helps avoid runtime exceptions and makes code more readable.

Your thoughts?