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
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),
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
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.
makeInt is written like this, it’s often used in a
makeInt(aString) match case Some(i) => println(s"Conversion worked. i = $i") case None => println("The conversion failed.")
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
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:
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
The exercises for this lesson are available here.
For a longer description, see my tutorial, Functional error handling in Scala.