← Scala EnglishChapter 11 of 13

Error Handling

## Learning Objectives - Master Option type for null safety - Work with Try for exception handling - Use Either for error messages - Understand for comprehensions with error types - Learn best practices for error handling ## Option Option represents a value that may or may not exist: ```scala val someValue: Option[Int] = Some(5) val noValue: Option[Int] = None ``` ### Creating Options ```scala // From nullable value val name: String = null val optionName: Option[String] = Option(name) // None val name2: String = "Alice" val optionName2: Option[String] = Option(name2) // Some("Alice") // From Map val map = Map("a" -> 1, "b" -> 2) map.get("a") // Some(1) map.get("c") // None ``` ### Extracting Values ```scala val option: Option[Int] = Some(42) // Pattern matching option match { case Some(value) => println(s"Got: $value") case None => println("No value") } // getOrElse val result = option.getOrElse(0) // 42 val noResult = None.getOrElse(0) // 0 // orElse val alt = None.orElse(Some(10)) // Some(10) // isDefined, isEmpty option.isDefined // true option.isEmpty // false ``` ### Map on Option ```scala val option: Option[Int] = Some(5) option.map(_ * 2) // Some(10) option.map(_ + 10) // Some(15) None.map(_ * 2) // None // FlatMap on Option def parseInt(s: String): Option[Int] = { try Some(s.toInt) catch case _: Exception => None } Some("42").flatMap(parseInt) // Some(42) Some("abc").flatMap(parseInt) // None None.flatMap(parseInt) // None ``` ### Filtering ```scala val option: Option[Int] = Some(5) option.filter(_ > 10) // None (5 not > 10) option.filter(_ > 3) // Some(5) // withFilter (doesn't wrap in Option) option.withFilter(_ > 10).foreach(println) // No output ``` ## Try Try represents computation that may throw an exception: ```scala import scala.util.{Success, Failure, Try} val success: Try[Int] = Success(42) val failure: Try[Int] = Failure(new Exception("Oops")) ``` ### Creating Try ```scala import scala.util.Try // From code that might throw def parseInt(s: String): Try[Int] = Try(s.toInt) parseInt("42") // Success(42) parseInt("abc") // Failure(NumberFormatException) // Using Try with Option def safeDivision(a: Int, b: Int): Try[Int] = Try(a / b) safeDivision(10, 2) // Success(5) safeDivision(10, 0) // Failure(ArithmeticException) ``` ### Try Extracting Values ```scala val tryValue: Try[Int] = Try(42 / 2) // Pattern matching tryValue match { case Success(v) => println(s"Got: $v") case Failure(ex) => println(s"Error: ${ex.getMessage}") } // getOrElse tryValue.getOrElse(0) // 21 // orElse def failing: Try[Int] = Try(throw new Exception("Oops")) failing.orElse(Success(0)) // Success(0) ``` ### Map and FlatMap on Try ```scala val tryValue: Try[Int] = Success(5) tryValue.map(_ * 2) // Success(10) tryValue.map(_ + 10) // Success(15) Failure(new Exception("Oops")).map(_ * 2) // Failure // FlatMap def safeSquare(x: Int): Try[Int] = Try { if (x > 10) throw new Exception("Too large") else x * x } Success(5).flatMap(safeSquare) // Success(25) Success(15).flatMap(safeSquare) // Failure ``` ### Recover ```scala def parseInt(s: String): Try[Int] = Try(s.toInt) parseInt("abc").recover { case _: NumberFormatException => 0 } // Success(0) parseInt("abc").recoverWith { case _: NumberFormatException => Try(42) } // Success(42) // Recover with partial function parseInt("abc").recoverWith { case _: NumberFormatException => Success(0) case _: Exception => Failure(...) } // Success(0) ``` ## Either Either represents one of two types: ```scala val left: Either[String, Int] = Left("Error") val right: Either[String, Int] = Right(42) ``` ### Creating Either ```scala def parseInt(s: String): Either[String, Int] = { try Right(s.toInt) catch case e: Exception => Left(e.getMessage) } parseInt("42") // Right(42) parseInt("abc") // Left("For input string: abc") ``` ### Either Extracting Values ```scala val either: Either[String, Int] = Right(42) // Pattern matching either match { case Right(v) => println(s"Got: $v") case Left(msg) => println(s"Error: $msg") } // getOrElse (only on Right) either.right.getOrElse(0) // 42 Left("error").right.getOrElse(0) // 0 // Swap either.swap // Left(42) - not useful here ``` ### Map on Either ```scala val right: Either[String, Int] = Right(5) right.map(_ * 2) // Right(10) right.map(_ + 10) // Right(15) Left("error").map(_ * 2) // Left("error") - preserved // FlatMap def safeSquare(x: Int): Either[String, Int] = { if (x > 10) Left("Too large") else Right(x * x) } Right(5).flatMap(safeSquare) // Right(25) Right(15).flatMap(safeSquare) // Left("Too large") ``` ### for Comprehensions with Either ```scala def parseInt(s: String): Either[String, Int] = try Right(s.toInt) catch case e: Exception => Left(e.getMessage) for { a <- parseInt("10") b <- parseInt("20") c <- parseInt("30") } yield a + b + c // Right(60) for { a <- parseInt("10") b <- parseInt("abc") // This fails c <- parseInt("30") } yield a + b + c // Left("For input string: abc") ``` ## For Comprehensions with Option ```scala case class User(name: String, email: Option[String]) def getUser(id: Int): Option[User] = ??? def getEmailDomain(email: String): Option[String] = ??? val domain = for { user <- getUser(1) email <- user.email domain <- getEmailDomain(email) } yield domain // Without for comprehension val domainAlt = getUser(1).flatMap { user => user.email.flatMap { email => getEmailDomain(email) } } ``` ## Chaining Operations ### Using fold ```scala def parseInt(s: String): Option[Int] = Try(s.toInt).toOption List("1", "2", "abc", "4") .map(parseInt) .fold(0)(_ + _) // 0 + 1 + 2 + 0 = 3 (abc becomes 0) // Or using collect List("1", "2", "abc", "4") .map(parseInt) .collect { case Some(n) => n } .sum // 7 ``` ### Using sequence ```scala import scala.util.TryingAnd Sequencing._ val options: List[Option[Int]] = List(Some(1), Some(2), Some(3)) val noneOptions: List[Option[Int]] = List(Some(1), None, Some(3)) options.sequence // Some(List(1, 2, 3)) noneOptions.sequence // None // traverse def parseInt(s: String): Option[Int] = Try(s.toInt).toOption List("1", "2", "3").traverse(parseInt) // Some(List(1, 2, 3)) List("1", "abc", "3").traverse(parseInt) // None ``` ## Best Practices 1. **Prefer Option over null** 2. **Use Try for code that throws** 3. **Use Either for expected failures with messages** 4. **Chain operations instead of nested if-else** 5. **Use for comprehensions for clean error handling** 6. **Don't catch Exception unnecessarily** 7. **Return meaningful error messages in Either** ## Summary - Option handles optional values (Some or None) - Try handles exceptions (Success or Failure) - Either handles expected failures (Left for error, Right for success) - map, flatMap, filter work on these types - for comprehensions provide clean syntax for chaining - Use getOrElse for providing default values - recover and recoverWith handle failures - sequence and traverse work with collections of error types

Comments

Comments powered by Giscus

To enable comments, add your Giscus embed code here.

Learn more about Giscus →