Errors in Scala

In Scala, it’s idiomatic to communicate errors via exceptions or Option/Either types. This contrasts with Go’s approach of using explicit error return values. Scala’s approach allows for more functional programming patterns and leverages the type system to handle errors.

import scala.util.{Try, Success, Failure}

// By convention, methods that might fail return a Try[T],
// Option[T], or Either[E, T] where T is the success type
// and E is the error type.
def f(arg: Int): Try[Int] = {
  if (arg == 42) {
    Failure(new IllegalArgumentException("can't work with 42"))
  } else {
    Success(arg + 3)
  }
}

// Custom exceptions can be used to represent specific error conditions
case class NoMoreTeaException(message: String) extends Exception(message)
case class CantBoilWaterException(message: String) extends Exception(message)

def makeTea(arg: Int): Try[String] = {
  if (arg == 2) {
    Failure(NoMoreTeaException("no more tea available"))
  } else if (arg == 4) {
    // In Scala, we don't need to explicitly wrap exceptions,
    // as they naturally form a chain
    Failure(new Exception("making tea", CantBoilWaterException("can't boil water")))
  } else {
    Success("Tea is ready!")
  }
}

object ErrorsExample extends App {
  for (i <- List(7, 42)) {
    f(i) match {
      case Success(r) => println(s"f worked: $r")
      case Failure(e) => println(s"f failed: ${e.getMessage}")
    }
  }

  for (i <- 0 until 5) {
    makeTea(i) match {
      case Success(result) => println(result)
      case Failure(e) =>
        // Pattern matching can be used to handle different types of exceptions
        e match {
          case NoMoreTeaException(_) => println("We should buy new tea!")
          case _: CantBoilWaterException => println("Now it is dark.")
          case _ => println(s"unknown error: ${e.getMessage}")
        }
    }
  }
}

To run the program, save it in a file named ErrorsExample.scala and use scala command:

$ scala ErrorsExample.scala
f worked: 10
f failed: can't work with 42
Tea is ready!
Tea is ready!
We should buy new tea!
Tea is ready!
Now it is dark.

In this Scala version:

  1. We use Try[T] to represent computations that might fail. Success(value) represents a successful computation, while Failure(exception) represents a failed one.

  2. Custom exceptions are defined as case classes extending Exception.

  3. Pattern matching is used extensively to handle different cases and exception types.

  4. The Option type (not shown in this example) is another common way to handle potential absence of a value in Scala.

  5. Scala’s Either[L, R] type (also not shown) can be used when you need to return either an error (conventionally the Left type) or a success value (the Right type).

This approach leverages Scala’s type system and functional programming features to handle errors in a type-safe and expressive way.