<jupyter_start><jupyter_text>Error handling part I. We have seen previously how exceptions are thrown. Technically `throw` is an expression that has a result type `Nothing`.<jupyter_code>def head[T](lst: List[T]): T = lst match {
  case Nil => throw new NoSuchElementException("No head element of an empty list")
  case x :: _ => x
}

val lst = List[Int](10, 20)

println(head(lst))
println(head(List[Int]()))
def stringToInt(s: String): Int = {
  try {
      s.toInt
  } catch {
      case f: java.io.FileNotFoundException => throw new Exception("File not found.")
      case e: java.lang.NumberFormatException =>
        throw new Exception("This string cannot be converted to a number.")
      case _ => throw new Exception("Some exception was thrown.")
  } finally {
    println("Connection to database has been closed.")
    42
  }
}
println(stringToInt("00123"))
println()
println(stringToInt("Hello"))<jupyter_output><empty_output><jupyter_text>Warning: do not use `return` statement in a `finally` clause! By the way, do not use `return` at all.<jupyter_code>def myFunc1(): Int = {
  try { 1 }
  finally { 42 }
}

def myFunc2(): Int = {
  try { return 1 }
  finally { return 42 }
}

println(myFunc1())
println(myFunc2())<jupyter_output><empty_output><jupyter_text>Error handling without exceptions Since exceptions are not type safe, let's catch exceptions at type level! Scala has several container types that are able to store a value that we might have failed to compute.* Option* Either* TryThe real power of these container types that all the usual higher order functions defined on them making chain of computations **seamless** even if something in the middle has failed. The `Option` data type replaces the following pattern:```pythondef my_python_func(x):    if not condition(x):        return None    y = do_computation(x)    return y``` ```scalasealed trait Option[+A]case class Some[+A](get: A) extends Option[A]case object None extends Option[Nothing]```<jupyter_code>def mean(xs: List[Double]): Option[Double] = xs match {
  case Nil => None
  case _ => Some(xs.sum / xs.length)
}
val lst = List(1.0, 2.0, 3.0)

mean(lst) match {
  case None => println("mean of empty list is not defined")
  case Some(m) => println(s"The mean is $m")
}<jupyter_output><empty_output><jupyter_text>Option is often the return type of a method defined on collections.<jupyter_code>val lst: List[Int] = List(1, 3, 5, 8, 9)
val firstEvenElem: Option[Int] = lst.find(_ % 2 == 0)

println(firstEvenElem)
println(firstEvenElem.isEmpty)
println(firstEvenElem.nonEmpty)

val m: Map[String, Int] = Map("a" -> 1, "b" -> 2)
println(m.get("a"))
case class User(id: Int, name: String, age: Option[Int])

val users: List[User] =
  List(
    User(1, "Adam", None),
    User(2, "Joe", Some(40)),
    User(3, "Jeff", Some(70)),
    User(9, "Sarah", None),
    User(10, "Bill", Some(120)),
    User(12, "Ivan", Some(32))
  )<jupyter_output><empty_output><jupyter_text>In the list above there are users with incorrect age attribute.Write a function called ```halfAge(users: List[User], id: Int): Option[Int]``` which receives an input ```id```, finds the user with that ```id``` in the list, and returns an optional age value which is one half of its original value.For example, the expected output in the following cases are:```scalahalfAge(users, 2) => Some(20)halfAge(users, 4) => None  // there is no user with that idhalfAge(users, 9) => None  // there is no age for that user``` The are some worse solutions than the worst one:<jupyter_code>type Age = Int

def halfAge(users: List[User], id: Int): Option[Age] = {
  val optionalUser: Option[User] = users.find(_.id == id)
  if (optionalUser.isEmpty) None
  else {
    val foundUser: User = optionalUser.get
    val optionalAge: Option[Int] = foundUser.age
    if (optionalAge.isEmpty) None
    else {
      val age: Int = optionalAge.get
      Some(age / 2) 
    }
  }
}
println(halfAge(users, 2))
println(halfAge(users, 4))
println(halfAge(users, 9))<jupyter_output><empty_output><jupyter_text>A better solution is to use pattern matching. Nevertheless, by writing code like these, I am sure that you know what ```Option```s are but you have no idea how to use them at all.<jupyter_code>def halfAge(users: List[User], id: Int): Option[Age] =
  users.find(_.id == id) match {
    case None => None
    case Some(user) =>
      user.age match {
        case None => None
        case Some(ageValue) => Some(ageValue/2)
      }
  }
println(halfAge(users, 2))
println(halfAge(users, 4))
println(halfAge(users, 9))<jupyter_output><empty_output><jupyter_text>How to write better code? There are higher order functions defined on the ```Option``` trait, like the ones below:```scalatrait Option[+A] {  def map[B](f: A => B): Option[B]    def flatMap[B](f: A => Option[B]): Option[B]    def filter(p: A => Boolean): Option[A]  }```<jupyter_code>def halfAge(users: List[User], id: Int): Option[Age] =
  users.find(_.id == id).flatMap(_.age).map(_ / 2)
println(halfAge(users, 2))
println(halfAge(users, 4))
println(halfAge(users, 9))
def halfAge(users: List[User], id: Int): Option[Age] =
  for {
    user <- users.find(_.id == id)
    age <- user.age
    } yield age / 2
println(halfAge(users, 2))
println(halfAge(users, 4))
println(halfAge(users, 9))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1375870506305289, 'fraction_numerical': 0.013928100884622624, 'mean_word_length': 4.4953464322647365, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 50, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '213141', 'license': 'MIT', 'n_tokens_mistral': 1824, 'n_tokens_neox': 1723, 'n_words': 582, 'path': 'Scala_tutorials/ScalaWorkShop/options.ipynb', 'repo_name': 'ghostrider77/presentations', 'size': '5313'}