-
sbt documentation (pdf)
-
show sbt history
-
scalatest - installing
-
scalatest - writing tdd tests
-
scalatest - writing bdd tests
-
scalatest - given/when/then with bdd
-
scalatest - test suite
-
scalatest - expected, actual
-
scalatest - mark test as pending
-
scalatest - testing exceptions
-
scalatest - tagging tests
-
scalatest - disabling tests
-
scalatest - mock objects
-
scalatest - running in eclipse
This is an excerpt from the 1st Edition of the
Scala Cookbook
(partially modified for the internet). This is Recipe 20.6, “Scala best practice: How to use the Option/Some/None pattern.”
Problem
For a variety of reasons, including removing
null
values from your
Scala
code, you want to use what I call the Option/Some/None pattern. Or, if you’re interested in a problem (exception) that occurred while processing code, you may want to return Try/Success/Failure from a method instead of Option/Some/None.
Solution
There is some overlap between this recipe and the previous recipe, “Eliminate null Values from Your Code”. That recipe shows how to use
Option
instead of
null
in the following situations:
-
Using
Option
in method and constructor parameters
-
Using
Option
to initialize class fields (instead of using
null
)
-
Converting
null
results from other code (such as Java code) into an
Option
See that recipe for examples of how to use an
Option
in those situations.
This recipe adds these additional solutions:
-
Returning an
Option
from a method
-
Getting the value from an
Option
-
Using
Option
with collections
-
Using
Option
with frameworks
-
Using Try/Success/Failure when you need the error message (Scala 2.10 and newer)
-
Using Either/Left/Right when you need the error message (pre-Scala 2.10)
Returning an Option from a Scala method
The
toInt
method used in this book shows how to return an
Option
from a method. It takes a
String
as input and returns a
Some[Int]
if the
String
is successfully converted to an
Int
, otherwise it returns a
None
:
def toInt(s: String): Option[Int] = {
try {
Some(Integer.parseInt(s.trim))
} catch {
case e: Exception => None
Here’s another way to write the same function:
import scala.util.control.Exception._
def toInt(s: String): Option[Int] = allCatch.opt(s.toInt)
Although this is a simple function, it shows the common pattern, as well as the syntax.
For a more complicated example, see the
readTextFile
example in Recipe 20.5. This is what
toInt
looks like in the REPL when it succeeds and returns a
Some
:
scala>
val x = toInt("1")
x: Option[Int] = Some(1)
This is what it looks like when it fails and returns a
None
:
scala>
val x = toInt("foo")
x: Option[Int] = None
Getting the value from an Option
The
toInt
example shows how to declare a method that returns an
Option
. As a consumer of a method that returns an
Option
, there are several good ways to call it and access its result:
-
Use
getOrElse
-
Use
foreach
-
Use a
match
expression
To get the actual value if the method succeeds, or use a default value if the method fails, use
getOrElse
:
scala>
val x = toInt("1").getOrElse(0)
x: Int = 1
Because an
Option
is a collection with zero or one elements, the
foreach
method can be used in many situations:
toInt("1").foreach{ i =>
println(s"Got an int: $i")
That example prints the value if
toInt
returns a
Some
, but bypasses the
println
statement if
toInt
returns a
None
.
Another good way to access the
toInt
result is with a
match
expression:
toInt("1") match {
case Some(i) => println(i)
case None => println("That didn't work.")
Using
Option
with Scala collections
Another great feature of
Option
is that it plays well with Scala collections. For instance, starting with a list of strings like this:
val bag = List("1", "2", "foo", "3", "bar")
imagine you want a list of all the integers that can be converted from that list of strings. By passing the
toInt
method into the
map
method, you can convert every element in the collection into a
Some
or
None
value:
scala>
bag.map(toInt)
res0: List[Option[Int]] = List(Some(1), Some(2), None, Some(3), None)
This is a good start. Because an
Option
is a collection of zero or one elements, you can convert this list of
Int
values by adding
flatten
to
map
:
scala>
bag.map(toInt).flatten
res1: List[Int] = List(1, 2, 3)
As shown in Recipe 10.16, “Combine
map
and
flatten
with
flatMap
”, this is the same as calling
flatMap
:
scala>
bag.flatMap(toInt)
res2: List[Int] = List(1, 2, 3)
The
collect
method provides another way to achieve the same result:
scala>
bag.map(toInt).collect{case Some(i) => i}
res3: List[Int] = List(1, 2, 3)
That example works because the
collect
method takes a
partial function
, and the anonymous function that’s passed in is only defined for
Some
values; it ignores the
None
values.
These examples work for several reasons:
-
toInt
is defined to return
Option[Int]
-
Methods like
flatten
,
flatMap
, and others are built to work well with
Option
values
-
You can pass anonymous functions into the collection methods
Using
Option
with other Scala frameworks
Once you begin working with third-party Scala libraries, you’ll see that
Option
is used to handle situations where a variable may not have a value. For instance, they’re baked into the Play Framework’s
Anorm
database library, where you use Option/Some/None for database table fields that can be
null
. In the following example, the third field may be
null
in the database, so it’s handled using
Some
and
None
, as shown:
def getAll() : List[Stock] = {
DB.withConnection { implicit connection =>
sqlQuery().collect {
// the 'company' field has a value
case Row(id: Int, symbol: String, Some(company: String)) =>
Stock(id, symbol, Some(company))
// the 'company' field does not have a value
case Row(id: Int, symbol: String, None) =>
Stock(id, symbol, None)
}.toList
The
Option
approach is also used extensively in Play validation methods:
verifying("If age is given, it must be greater than zero",
model =>
model.age match {
case Some(age) => age < 0
case None => true
The
scala.util.control.Exception
object gives you another way to use an
Option
, depending on your preferences and needs. For instance, the try/catch block was removed from the following method and replaced with an
allCatch
method:
import scala.util.control.Exception._
def readTextFile(f: String): Option[List[String]] = allCatch.opt(Source.fromFile(f).getLines.toList)
allCatch
is described as a
Catch
object “that catches everything.” The
opt
method returns
None
if an exception is caught (such as a
FileNotFoundException
), and a
Some
if the block of code succeeds.
Other
allCatch
methods support the
Try
and
Either
approaches. See the
Exception
object Scaladoc for more information.
If you like the Option/Some/None approach, but want to write a method that returns error information in the failure case (instead of None, which doesn’t return any error information), there are two similar approaches:
-
Try
,
Success
, and
Failure
(introduced in Scala 2.10)
-
Either
,
Left
, and
Right
I prefer the new Try/Success/Failure approach, so let’s look at it next.
Using
Try
,
Success
, and
Failure
Scala 2.10 introduced
scala.util.Try
as an approach that’s similar to
Option
, but returns failure information rather than a
None
.
The result of a computation wrapped in a
Try
will be one of its subclasses:
Success
or
Failure
. If the computation succeeds, a
Success
instance is returned; if an exception was thrown, a
Failure
will be returned, and the
Failure
will hold information about what failed.
To demonstrate this, first import the new classes:
import scala.util.{Try,Success,Failure}
Then create a simple method:
def divideXByY(x: Int, y: Int): Try[Int] = {
Try(x / y)
This method returns a successful result as long as
y
is not zero. When
y
is zero, an
ArithmeticException
happens. However, the exception isn’t thrown out of the method; it’s caught by the
Try
, and a
Failure
object is returned from the method.
The method looks like this in the REPL:
scala>
divideXByY(1,1)
res0: scala.util.Try[Int] = Success(1)
scala>
divideXByY(1,0)
res1: scala.util.Try[Int] = Failure(java.lang.ArithmeticException: / by zero)
As with an
Option
, you can access the
Try
result using
getOrElse
, a
foreach
method, or a
match
expression. If you don’t care about the error message and just want a result, use
getOrElse
:
// Success
scala>
val x = divideXByY(1, 1).getOrElse(0)
x: Int = 1
// Failure
scala>
val y = divideXByY(1, 0).getOrElse(0)
y: Int = 0
Using a
foreach
method also works well in many situations:
scala>
divideXByY(1, 1).foreach(println)
scala>
divideXByY(1, 0).foreach(println)
(no output printed)
If you’re interested in the
Failure
message, one way to get it is with a
match
expression:
divideXByY(1, 1) match {
case Success(i) => println(s"Success, value is: $i")
case Failure(s) => println(s"Failed, message is: $s")
Another approach is to see if a
Failure
was returned, and then call its
toString
method (although this doesn’t really follow the “Scala way”):
scala>
if (x.isFailure) x.toString
res0: Any = Failure(java.lang.ArithmeticException: / by zero)
The
Try
class has the added benefit that you can chain operations together, catching exceptions as you go. For example, the following code won’t throw an exception, regardless of what the values of
x
and
y
are:
val z = for {
a <- Try(x.toInt)
b <- Try(y.toInt)
} yield a * b
val answer = z.getOrElse(0) * 2
If
x
and
y
are
String
values like "1" and "2", this code works as expected, with
answer
resulting in an
Int
value. If
x
or
y
is a
String
that can’t be converted to an
Int
,
z
will have this value:
z: scala.util.Try[Int] = Failure(java.lang.NumberFormatException: For input string: "one")
If
x
or
y
is
null
,
z
will have this value:
z: scala.util.Try[Int] = Failure(java.lang.NumberFormatException: null)
In either
Failure
case, the
getOrElse
method protects us, returning the default value of
0
.
The
readTextFile
method in Recipe 20.5 shows another
Try
example. The method from that example is repeated here:
def readTextFile(filename: String): Try[List[String]] = {
Try(Source.fromFile(filename).getLines.toList)
If the
readTextFile
method runs successfully, the lines from the
/etc/passwd
file are printed, but if an exception happens while trying to open and read the file, the
Failure
line in the
match
expression prints the error, like this:
java.io.FileNotFoundException: Foo.bar (No such file or directory)
The
Try
class includes a nice collection of methods that let you handle situations in many ways, including:
-
Collection-like implementations of
filter
,
flatMap
,
flatten
,
foreach
, and
map
-
get
,
getOrElse
, and
orElse
-
toOption
, which lets you treat the result as an
Option
-
recover
,
recoverWith
, and
transform
, which let you gracefully handle
Success
and
Failure
results
As you can see,
Try
is a powerful alternative to using Option/Some/None.
Using
Either
,
Left
, and
Right
Prior to Scala 2.10, an approach similar to
Try
was available with the
Either
,
Left
, and
Right
classes. With these classes,
Either
is analogous to
Try
,
Right
is similar to
Success
, and
Left
is similar to
Failure
.
The following method demonstrates how to implement the
Either
approach:
def divideXByY(x: Int, y: Int): Either[String, Int] = {
if (y == 0) Left("Dude, can't divide by 0")
else Right(x / y)
As shown, your method should be declared to return an
Either
, and the method body should return a
Right
on success and a
Left
on failure. The
Right
type is the type your method returns when it runs successfully (an
Int
in this case), and the
Left
type is typically a
String
, because that’s how the error message is returned.
As with
Option
and
Try
, a method returning an
Either
can be called in a variety of ways, including
getOrElse
or a
match
expression:
val x = divideXByY(1, 1).right.getOrElse(0) // returns 1
val x = divideXByY(1, 0).right.getOrElse(0) // returns 0
// prints "Answer: Dude, can't divide by 0"
divideXByY(1, 0) match {
case Left(s) => println("Answer: " + s)
case Right(i) => println("Answer: " + i)
You can also access the error message by testing the result with
isLeft
, and then accessing the left value, but this isn’t really the Scala way:
scala>
val x = divideXByY(1, 0)
x: Either[String,Int] = Left(Dude, can't divide by 0)
scala>
x.isLeft
res0: Boolean = true
scala>
x.left
res1: scala.util.Either.LeftProjection[String,Int] =
LeftProjection(Left(Dude, can't divide by 0))
Discussion
As shown in the Solution, if there’s a weakness of using
Option
, it’s that it doesn’t tell you
why
something failed; you just get a
None
instead of a
Some
. If you need to know why something failed, use
Try
instead of
Option
.
Don’t use the
get
method with
Option
When you first come to Scala from Java, you may be tempted to use the
get
method to access the result:
scala>
val x = toInt("5").get
x: Int = 5
However, this isn’t any better than a
NullPointerException
:
scala>
val x = toInt("foo").get
java.util.NoSuchElementException: None.get
// long stack trace omitted ...
Your next thought might be to test the value before trying to access it:
// don't do this
scala>
val x = if (toInt("foo").isDefined) toInt("foo") else 0
x: Any = 0
As the comment says, don’t do this. In short, it’s a best practice to never call
get
on an
Option
. The preferred approaches are to use
getOrElse
, a
match
expression, or
foreach
. (As with
null
values, I just imagine that
get
doesn’t exist.)
-
-
-
-
-
is owned and operated by
Valley Programming, LLC
In regards to links to Amazon.com, As an Amazon Associate
I (Valley Programming, LLC) earn from qualifying purchases
This website uses cookies:
learn more