

Type-safe error handling with Scala 3
source link: https://xebia.com/blog/type-safe-error-handling-with-scala-3/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Introduction
In a previous blog post, we looked at type-safe error handling with Shapeless coproducts and realized that coproducts make up for the lack of union types in Scala 2.x. A coproduct can be seen as an EitherN
to return either n
types. In that regard, there’s no limit on the number of types we can use, in contrast to, for example, an Either[A, B]
.
The challenge with Shapeless coproducts is that although they work perfectly, the code is not as clean as it could be due to the lack of native support for union types. However, this changed in Scala 3! In this article, I will demonstrate that we can create the same typed error channel as in the previous blog post, but this time with way less code and improved readability!
Why a typed error channel in the first place?
A typed error channel explicitly shows the programmer what kind of errors can appear and thus immediately understands from the signature which cases he needs to handle. These handlers can become simple functions that are easy to test.
If we look at web development, several errors are guaranteed to happen:
- Input validation errors; Should be mapped back to the user so that the person can correct his input.
- Domain validation errors; Depending on the error, you might not want to notify the user itself but rather trigger an alert for a support employee to fix the issue.
- Unexpected exceptions; Should be logged, trigger an alert, and you should inform the user that "something went wrong". For example, you wouldn’t want potential sensitive data leaking to the outside world.
Code example
Let’s have a look at the code:
import scala.io.StdIn.readLine
object DivideCommandLineApp extends App {
case class ParseNumberError(value: String)
case object DivideByZeroError
def tryParse(s: String): Either[ParseNumberError, Double] =
s.toDoubleOption.fold[Either[ParseNumberError, Double]](Left(ParseNumberError(s)))(a => Right(a))
def tryDivide(a: Double, b: Double): Either[DivideByZeroError.type, Double] =
if (b == 0) Left(DivideByZeroError)
else Right(a / b)
def tryRunApp: Either[ParseNumberError | DivideByZeroError.type, Double] = for {
a <- tryParse(readLine)
b <- tryParse(readLine)
r <- tryDivide(a, b)
} yield r
tryRunApp.fold({
case ParseNumberError(error) => println(s"Error: Input '$error' is not a number")
case DivideByZeroError => println("Error: Cannot divide by zero")
}, r => println(s"Result: $r"))
}
By leveraging the Either
type, we do not need anything fancier. As Either
already is a typed error channel, we can use a union type on the left side. It composes well, as seen in the tryRunApp
method. It returns either a ParseNumberError
or a DivideByZeroError
.
Voilà! That’s how easily you can have error channels in Scala 3 with the help of union types.
Differences
Let’s have a quick objective look at the differences between the code that uses Shapeless coproducts and the code below:
- Around 70% reduction of code (81 lines vs 24)
- No need for an external library (i.e. Shapeless)
- No need for implicits (not that I’m against implicits, but it can confuse new programmers in Scala quite a bit)
Conclusion
Using union types in Scala 3 brings new opportunities for modelling our domains and our ways of handling errors. There’s no need to use Shapeless coproducts for these simple union type constructions in Scala 3. Does this mean that Shapeless became obsolete? Of course not! There’s still a lot of functionality in Shapeless not covered by Scala 3.
Recommend
-
30
I’ve been learning more about Scala’s type system, and last week, I hit some of its limitations. In this post, I’ll be covering an issue called type erasure . If you’re interested in understanding gen...
-
11
1. Overview The Scala programming language has many ways to handle dependencies among types. The so-called “self-type” annotation allows us to declare dependencies using traits and the concept of mixins. In this tutorial, we...
-
6
Strategic Scala Style: Practical Type Safety Posted 2016-04-30This post explores how you can make use of the type-safety...
-
10
scala_5_type_classintrodef insertionSort(xs: List[Int]): List[Int] = { def insert(y: Int, ys: List[Int]): List[Int] = ys match { case List() => y :: List() case z :: zs => if (y...
-
9
Idiomatic Error Handling in Scala Reading Time: 3 minutesError handling in Scala can just be written like Java. Put in a little bit of pattern matching magic and you are done. However, given a little use of Scal...
-
4
Dec 26, 2012The Neophyte's Guide to Scala Part 6: Error handling with Try Reading time: about 12 minutes When just playing around with a new language, you might get away with simply ignoring the fact that something might go wro...
-
17
Type Classes in Scala 3 Type Classes in Scala 3 Table of Contents I'm recently migrating some libs and projects to Scala 3, I guess it would be very helpful to me or anyone interested to learn some new functiona...
-
2
Knoldus Blogs scala-exception-handling Reading Time: 6 minutes Hi all, in this blog we are going to observe how to do exception handling in Scala. To all my friends coming from Java background, bear with me this...
-
5
Handling exceptions in Scala August 29, 2017 | 9 Minute Read Java exceptions are one of the most controversial Java language featur...
-
7
IntroductionError handling is often not given the attention that it deserves. Coming from imperative languages, it’s super easy to throw exceptions all over the place. Still, because it’s easy, it doesn’t mean it’s a goo...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK