Scala 3 — Book

# Functional Error Handling

Language

Functional programming is like writing a series of algebraic equations, and because algebra doesn’t have null values or throw exceptions, you don’t use these features in FP. This brings up an interesting question: In the situations where you might normally use a null value or exception in OOP code, what do you do?

Scala’s solution is to use constructs like the `Option`/`Some`/`None` classes. This lesson provides an introduction to using these techniques.

Two notes before we jump in:

• The `Some` and `None` classes are subclasses of `Option`.
• Instead of repeatedly saying “`Option`/`Some`/`None`,” the following text generally just refers to “`Option`” or “the `Option` classes.”

## A first example

While this first example doesn’t deal with null values, it’s a good way to introduce the `Option` classes, so we’ll start with it.

Imagine that you want to write a method that makes it easy to convert strings to integer values, and you want an elegant way to handle the exception that’s thrown when your method gets a string like `"Hello"` instead of `"1"`. A first guess at such a method might look like this:

``````def makeInt(s: String): Int =
try {
Integer.parseInt(s.trim)
} catch {
case e: Exception => 0
}
``````
``````def makeInt(s: String): Int =
try
Integer.parseInt(s.trim)
catch
case e: Exception => 0
``````

If the conversion works, this method returns the correct `Int` value, but if it fails, the method returns `0`. This might be okay for some purposes, but it’s not really accurate. For instance, the method might have received `"0"`, but it may have also received `"foo"`, `"bar"`, or an infinite number of other strings that will throw an exception. This is a real problem: How do you know when the method really received a `"0"`, or when it received something else? The answer is that with this approach, there’s no way to know.

## Using Option/Some/None

A common solution to this problem in Scala is to use a trio of classes known as `Option`, `Some`, and `None`. The `Some` and `None` classes are subclasses of `Option`, so the solution works like this:

• You declare that `makeInt` returns an `Option` type
• If `makeInt` receives a string it can convert to an `Int`, the answer is wrapped inside a `Some`
• If `makeInt` receives a string it can’t convert, it returns a `None`

Here’s the revised version of `makeInt`:

``````def makeInt(s: String): Option[Int] =
try {
Some(Integer.parseInt(s.trim))
} catch {
case e: Exception => None
}
``````
``````def makeInt(s: String): Option[Int] =
try
Some(Integer.parseInt(s.trim))
catch
case e: Exception => None
``````

This code can be read as, “When the given string converts to an integer, return the `Int` wrapped inside a `Some`, such as `Some(1)`. When the string can’t be converted to an integer, an exception is thrown and caught, and the method returns a `None` value.”

These examples show how `makeInt` works:

``````val a = makeInt("1")     // Some(1)
val b = makeInt("one")   // None
``````

As shown, the string `"1"` results in a `Some(1)`, and the string `"one"` results in a `None`. This is the essence of the `Option` approach to error handling. As shown, this technique is used so methods can return values instead of exceptions. In other situations, `Option` values are also used to replace `null` values.

Two notes:

• You’ll find this approach used throughout Scala library classes, and in third-party Scala libraries.
• A key point of this example is that functional methods don’t throw exceptions; instead they return values like `Option`.

## Being a consumer of makeInt

Now imagine that you’re a consumer of the `makeInt` method. You know that it returns a subclass of `Option[Int]`, so the question becomes, how do you work with these return types?

There are two common answers, depending on your needs:

• Use a `match` expression
• Use a `for` expression

## Using a `match` expression

One possible solution is to use a `match` expression:

``````makeInt(x) match {
case Some(i) => println(i)
case None => println("That didn’t work.")
}
``````
``````makeInt(x) match
case Some(i) => println(i)
case None => println("That didn’t work.")
``````

In this example, if `x` can be converted to an `Int`, the expression on the right-hand side of the first `case` clause is evaluated; if `x` can’t be converted to an `Int`, the expression on the right-hand side of the second `case` clause is evaluated.

## Using a `for` expression

Another common solution is to use a `for` expression—i.e., the `for`/`yield` combination that was shown earlier in this book. For instance, imagine that you want to convert three strings to integer values, and then add them together. This is how you do that with a `for` expression and `makeInt`:

``````val y = for {
a <- makeInt(stringA)
b <- makeInt(stringB)
c <- makeInt(stringC)
} yield {
a + b + c
}
``````
``````val y = for
a <- makeInt(stringA)
b <- makeInt(stringB)
c <- makeInt(stringC)
yield
a + b + c
``````

After that expression runs, `y` will be one of two things:

• If all three strings convert to `Int` values, `y` will be a `Some[Int]`, i.e., an integer wrapped inside a `Some`
• If any of the three strings can’t be converted to an `Int`, `y` will be a `None`

You can test this for yourself:

``````val stringA = "1"
val stringB = "2"
val stringC = "3"

val y = for {
a <- makeInt(stringA)
b <- makeInt(stringB)
c <- makeInt(stringC)
} yield {
a + b + c
}
``````
``````val stringA = "1"
val stringB = "2"
val stringC = "3"

val y = for
a <- makeInt(stringA)
b <- makeInt(stringB)
c <- makeInt(stringC)
yield
a + b + c
``````

With that sample data, the variable `y` will have the value `Some(6)`.

To see the failure case, change any of those strings to something that won’t convert to an integer. When you do that, you’ll see that `y` is a `None`:

``````y: Option[Int] = None
``````

## Thinking of Option as a container

Mental models can often help us understand new situations, so if you’re not familiar with the `Option` classes, one way to think about them is as a container:

• `Some` is a container with one item in it
• `None` is a container, but it has nothing in it

If you prefer to think of the `Option` classes as being like a box, `None` is like an empty box. It could have had something in it, but it doesn’t.

## Using `Option` to replace `null`

Getting back to `null` values, a place where a `null` value can silently creep into your code is with a class like this:

``````class Address(
var street1: String,
var street2: String,
var city: String,
var state: String,
var zip: String
)
``````

While every address on Earth has a `street1` value, the `street2` value is optional. As a result, the `street2` field can be assigned a `null` value:

``````val santa = new Address(
"1 Main Street",
null,               // <-- D’oh! A null value!
"North Pole",
"99705"
)
``````
``````val santa = Address(
"1 Main Street",
null,               // <-- D’oh! A null value!
"North Pole",
"99705"
)
``````

Historically, developers have used blank strings and null values in this situation, both of which are hacks to work around the root problem: `street2` is an optional field. In Scala—and other modern languages—the correct solution is to declare up front that `street2` is optional:

``````class Address(
var street1: String,
var street2: Option[String],   // an optional value
var city: String,
var state: String,
var zip: String
)
``````

Now developers can write more accurate code like this:

``````val santa = new Address(
"1 Main Street",
None,           // 'street2' has no value
"North Pole",
"99705"
)
``````
``````val santa = Address(
"1 Main Street",
None,           // 'street2' has no value
"North Pole",
"99705"
)
``````

or this:

``````val santa = new Address(
"123 Main Street",
Some("Apt. 2B"),
"Talkeetna",
"99676"
)
``````
``````val santa = Address(
"123 Main Street",
Some("Apt. 2B"),
"Talkeetna",
"99676"
)
``````

## `Option` isn’t the only solution

While this section focuses on the `Option` classes, Scala has a few other alternatives.

For example, a trio of classes known as `Try`/`Success`/`Failure` work in the same manner, but (a) you primarily use these classes when your code can throw exceptions, and (b) you want to use the `Failure` class because it gives you access to the exception message. For example, these `Try` classes are commonly used when writing methods that interact with files, databases, and internet services, as those functions can easily throw exceptions.

## A quick review

This section was long, so let’s give it a quick review:

• Functional programmers don’t use `null` values
• A main replacement for `null` values is to use the `Option` classes
• Functional methods don’t throw exceptions; instead they return values like `Option`, `Try`, or `Either`
• Common ways to work with `Option` values are `match` and `for` expressions
• Options can be thought of as containers of one item (`Some`) and no items (`None`)
• Options can also be used for optional constructor or method parameters