NOTE: This is a chapter from my book, Learn Scala 3 The Fast Way. Due to a mistake, this lesson was not included in the book.
When you write functions, things can go wrong. In other languages you might throw exceptions or return null values, but in Scala you don’t do those things. (Technically you can, but other people won’t be happy with your code.)
Instead what we do is work with error-handling data types. To demonstrate this I’ll create a function to convert a String
to an Int
.
Background
Technically you can already convert a String
to an Int
already because the String
class has a toInt
method. The REPL shows how it succeeds and how it fails:
scala> "1".toInt
val res0: Int = 1
scala> "one".toInt
java.lang.NumberFormatException: For input string: "one"
In the first example the string properly represents an integer — "1"
— so the conversion process works. But when the string can’t be converted to an integer in the second example, the process blows up with a NumberFormatException
. This is bad.
A “String to Int” method
Knowing what we know so far, our first attempt to write a makeInt
function might look like this:
def makeInt(s: String): Int =
try
s.toInt
catch
case e: NumberFormatException => 0
That function will always return an Int
, but the problem is that it isn’t accurate in all instances. For example, all of these method calls will return 0
, which is incorrect:
makeInt("0")
makeInt("")
makeInt("zero")
In the first example the value returned is correct, but in the second and third examples, 0
is not the correct answer. So, what is the correct solution?
The correct solution
This is where Scala’s error-handling data types come in. Instead of the function returning an Int
, the correct approach is for the function to return a data type that encapsulates the possibility of both success and failure. One such data type is the Option
type, and its sub-types (children), Some
and None.
The general solution is to (a) define your function to return an Option
that wraps the desired return type — Int
, in this case. And then in the body of the function, you return (b) a Some
for the success case, and (c) a None
for the failure case:
def makeInt(s: String): Option[Int] =
try
Some(s.toInt)
catch
case e: NumberFormatException => None
As shown, the Some
wraps the data type that you were able to calculate successfully, and in the error case you return a None
. This is how the function works:
makeInt("1") // Some(1)
makeInt("one") // None
The key to this solution is that Option
is a base type, and Some
and None
are its sub-types. So the function is defined to return the parent type: Option
, or specifically an Option[Int]
in this case. Then the body of the function returns either of those sub-types: Some
for the success case, and None
for the error/failure case.
Now that makeInt
is written like this, it’s often used in a match
expression:
makeInt(aString) match
case Some(i) => println(s"Conversion worked. i = $i")
case None => println("The conversion failed.")
The makeInt
function can be written more concisely, but when you’re first starting out it helps to see this long form.
Other error-handling types
One problem with the Option
/Some
/None
approach is that None
doesn’t give the caller of your function any information on what went wrong. It just indicates “An error happened,” but doesn’t provide any details.
For situations where you want your function to give its users those details, Scala has these additional error-handling data types:
Try
/Success
/Failure
Either
/Left
/Right
These are demonstrated in the next lesson.
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
Exercises
The exercises for this lesson are available here.
More information
For a longer description, see my tutorial, Functional error handling in Scala.
And if you like videos, see my free “Functional Error Handlingin Scala“ training video.