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
This lesson provides an introduction to using these techniques.
Two notes before we jump in:
Noneclasses are subclasses of
- Instead of repeatedly saying “
None,” the following text generally just refers to “
Option” or “the
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
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
If the conversion works, this method returns the correct
Int value, but if it fails, the method returns
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
"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.
A common solution to this problem in Scala is to use a trio of classes known as
None classes are subclasses of
Option, so the solution works like this:
- You declare that
makeIntreceives a string it can convert to an
Int, the answer is wrapped inside a
makeIntreceives a string it can’t convert, it returns a
Here’s the revised version of
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
When the string can’t be converted to an integer, an exception is thrown and caught, and the method returns a
These examples show how
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
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
- 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
Being a consumer of makeInt
Now imagine that you’re a consumer of the
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
- Use a
One possible solution is to use a
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.
Another common solution is to use a
for expression—i.e., the
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
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
ywill be a
Some[Int], i.e., an integer wrapped inside a
- If any of the three strings can’t be converted to an
ywill be a
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
With that sample data, the variable
y will have the value
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
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:
Someis a container with one item in it
Noneis 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.
Option to replace
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
val santa = Address( "1 Main Street", null, // <-- D’oh! A null value! "North Pole", "Alaska", "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 = Address( "1 Main Street", None, // 'street2' has no value "North Pole", "Alaska", "99705" )
val santa = Address( "123 Main Street", Some("Apt. 2B"), "Talkeetna", "Alaska", "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
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
- A main replacement for
nullvalues is to use the
- Functional methods don’t throw exceptions; instead they return values like
- Common ways to work with
- Options can be thought of as containers of one item (
Some) and no items (
- Options can also be used for optional constructor or method parameters