पाठ 11 / 25
Option, Either and Try
Handle missing values and errors as values instead of null and exceptions.
Errors as values
Idiomatic Scala avoids null and uses types to make failure visible. Option[A] is Some(value) or None for values that may be absent, such as map.get(key) or list.headOption. Either[E, A] is Left(error) or Right(value), ideal for validation and domain errors, with the error type documenting what can go wrong; Either is right-biased, so map and flatMap work on the success side. Try[A] is Success(value) or Failure(throwable), wrapping code that may throw, such as parsing or Java APIs. All three support map, flatMap, getOrElse, fold and for comprehensions, so a sequence of fallible steps stops at the first failure without nested if checks. Convert between them with toOption, toEither and toRight(error). Reserve exceptions for truly unexpected failures, and wrap Java code that may return null with Option(javaValue). Libraries such as Cats add Validated to accumulate all validation errors rather than stopping at the first.
Validating a signup with Either
Each step can fail; the for comprehension short-circuits on the first Left.
import scala.util.Try
case class Signup(name: String, email: String, age: Int)
enum SignupError:
case MissingName
case InvalidEmail(value: String)
case InvalidAge(value: String)
def validName(n: String): Either[SignupError, String] =
if n.trim.nonEmpty then Right(n.trim) else Left(SignupError.MissingName)
def validEmail(e: String): Either[SignupError, String] =
Either.cond(e.contains("@"), e.toLowerCase, SignupError.InvalidEmail(e))
def validAge(raw: String): Either[SignupError, Int] =
Try(raw.toInt).toOption.filter(a => a >= 13 && a <= 120).toRight(SignupError.InvalidAge(raw))
def signup(form: Map[String, String]): Either[SignupError, Signup] =
for
name <- validName(form.getOrElse("name", ""))
email <- validEmail(form.getOrElse("email", ""))
age <- validAge(form.getOrElse("age", ""))
yield Signup(name, email, age)
println(signup(Map("name" -> "Asha", "email" -> "ASHA@example.com", "age" -> "29")))
// Right(Signup(Asha,asha@example.com,29))
println(signup(Map("name" -> "Ravi", "email" -> "ravi", "age" -> "x")))
// Left(InvalidEmail(ravi))
val maybeCity: Option[String] = Option(System.getenv("CITY")) // wraps a possibly-null Java value
println(maybeCity.map(_.toUpperCase).getOrElse("UNKNOWN"))Labelled parcels
Option, Either and Try are parcels with a label on the outside: "maybe empty", "success or a reason for failure", "success or an exception". You must look at the label before you can use the contents, so nothing explodes in your hands unexpectedly.
त्वरित जाँच: When is Either[E, A] a better choice than Option[A]?
- When you do not care why something failed
- When you need to report why a computation failed
- When values can never be missing
- When working only with Java code
Answer
When you need to report why a computation failed — Either carries an error value on the Left side, while None carries no reason.