Functional Programming In Scala (1) — Options

Mallika Kulkarni

Scala Options

Everybody who has ever worked in Java has come across the NullPointerException. The NullPointerException is a kind of runtime exception you get when a method actually returns a null value and your code was not expecting it, and thus, you neglected to handle that scenario appropriately. In Java, null is often misused to represent an absent Optional value.

Scala tries to solve this problem by doing away with null altogether. It provides its own type for representing optional values — the Option[A] class. Option[A] is a container for the optional value of type A. If the value is present, the Option[A] is a value of type Some[A]; if not, it is an instance of None.

The advantage of using option is that you bake in the optional nature of the value at compile time. Other developers who depend on this value are forced by the compiler to deal with this in their code. There is no way anybody can rely on the presence of a purely optional value.

How do you create an Option:

Directly instantiate a case class:

val someOption : Option[String] = Some("Present Value")val noneOption : Option[String] = None

On the other side of the table, this is how you actually work with an Option.

case class Employee (
id: Int,
name: String,
gender: Option[String],
tenure: Int
)
object EmployeeDB {
private val employees = Map(
1 -> Employee(1, "John Deer", Some("Male"), 5),
2 -> Employee(2, "Jane Doe", None, 3)
)

def findEmployeeById(id: Int) : Option[Employee] = {
employees.get(id)
}
}

This piece of code represents a database of your employees. Each employee has an id, name, optionally a gender specified and the tenure with the employer. The employee database is stored in memory with a mapping of the employee id to the employee object.

The findEmployeeById accepts an employee id. If the id is valid, it returns Option[Employee]. If not, it returns None.

If your code is a consumer of this function, you can access the return value if this function in quite a few ways.

  1. Use a default value
val employee = Employee(2, "Jane Doe", None, 3)
println(employee.gender.getOrElse("Not specified"))

You might want to define a fallback or default value when the optional value is not present. You can use the getOrElse function to do it.

2. Check the value with the isDefined function, and get it via get

val resOpt = EmployeeDB.findEmployeeById(1)
if (resOpt.isDefined)
println(resOpt.get.name) //Prints John Deer

You will notice that this is very similar to how you would process an Optional null value in Java

Employee res = EmployeeDB.findEmployeeById(1);
if (res != null) {
System.out.Println(res.name)
}

Obviously this method does not offer significant advantages of using an Option instead of a null value.

3. Pattern Matching

val employee = Employee(2, "Jane Doe", None, 3)
employee.gender match {
case Some(gender) => println(gender)
case None => println("Not specified")
}

Some is a case class, so it is acceptable to use it in a pattern. Simplifying the above expression to avoid code redundancy

val employee = Employee(2, "Jane Doe", None, 3)
val gender = employee.gender match {
case Some(spec) => spec
case None => "Not specified"
}
println(gender)

4. Option as a collection

The Option[A] can be viewed as a very special kind of collection. It contains either 0 elements or exactly 1 element of type A. This obviously opens up a number of possibilities for working with the Option.

The function passed to foreach will be called exactly once if the Option is present, never if it is not

EmployeeDB.findEmployeeById(1).foreach(employee => println(employee.name))

You can also map an Option[A] to an Option[B] i.e your instance of Option[A] will b emapped to Option[B] if it is present, if it is not, you get a None Option[B]

val nameOpt = EmployeeDB.findEmployeeById(1).map(_.name) // nameOpt = Some("John Deer")

If we do the same thing for gender, we get

val genderOpt = EmployeeDB.findEmployeeById(1).map(_.gender)

What you get for the above is an Option[Option[String]]. If you want to flatten this nested option, you use a flatMap.

val gender1 = EmployeeDB.findEmployeeById(1).flatMap(_.gender) //gender is Some("Male")val gender2 = EmployeeDB.findEmployeeById(2).flatMap(_.gender) //gender is None

The result for the code above is an Option[String]. When the employee is defined, and the employee’s gender is defined, you get a present optional value. If either is not defined, you get None .

So, what happens if you need to flatten a list of Options.

val numberOpts = List(Some(1), None, Some(2), None)
val numbers = numberOpts.flatten.map(num => num * 2) // List(1, 2)

You can also filter an Option. If the Option[A] is defined, and the predicate passed to filter is true from the value of the option, the Some instance is returned.

EmployeeDB.findEmployeeById(1).filter(_.tenure >= 5) //Some(employee) because tenure = 5EmployeeDB.findEmployeeById(2).filter(_.tenure >= 5) //None because tenure = 3

Like always, you can also use a for comprehension to make things more readable

for {
employee <- EmployeeDB.findEmployeeById(1)
gender <- employee.gender
} yield {
println(gender)
} // prints Some("Male")

That is almost all the Option stuff that you will use regularly.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade