7. Usage
// case class Result(status: String, users: List[User], reason: String)
def happilyUsingMyResults(result: Result) = {
log(s"Result: ${result.status.toUpperCase}
Reason: ${result.reason.toUpperCase}")
result.users.foreach(
user => log(s"User email: ${user.email.toLowerCase}
User name: ${user.name}")
)
}
Null pointer exceptions everywhere!
8. Implementation with Options
case class User(email: String, name: Option[String])
case class Result(
status: String,
users: Option[List[User]],
reason: Option[String]
)
9. Is it any better?
def happilyUsingMyResults(result: Result) = {
val reason = result.reason.map(reason => s"Reason: $reason").getOrElse("")
println(s"Result: ${result.status.toUpperCase} - Reason: $reason")
result.users.foreach(
_.foreach(
user => {
val username = user.name.map(name => s"User name: $name").getOrElse("")
log(s"User email: ${user.email.toLowerCase} $username")
}
)
)
}
10. Typed implementation
case class User(email: String, name: Option[String])
sealed trait Result {
val status: String
}
case class Success(status: String, users: List[User]) extends Result
case class Failure(status: String, reason: String) extends Result
11. def happilyUsingMyResults(result: Result) = {
result match {
case Success(status, users) => {
log(s"Result: $status")
users.foreach(user => {
val usernameString =
user.name.map(name => s"User name: $name").getOrElse("")
log(s"User email: ${user.email.toLowerCase} $usernameString")
})
}
case Failure(status, reason) =>
log(s"Result: $status Reason: $reason")
}
}
12. TLDR;
• Use types to represent the state of your
program
• Unreachable states should not be
representable
• Handle these concerns at the borders