SlideShare a Scribd company logo
OPTION, EITHER, TRY
AND WHAT TO DO WITH CORNER CASES WHEN
THEY ARISE
KNOW YOUR LIBRARY MINI-SERIES
By /Michal Bigos @teliatko
KNOW YOUR LIBRARY - MINI SERIES
1. Hands-on with types from Scala library
2. DO's and DON'Ts
3. Intended for rookies, but Scala basic syntax assumed
4. Real world use cases
WHY NULL ISN'T AN OPTION
CONSIDER FOLLOWING CODE
String foo = request.params("foo")
if (foo != null) {
String bar = request.params("bar")
if (bar != null) {
doSomething(foo, bar)
} else {
throw new ApplicationException("Bar not found")
}
} else {
throw new ApplicationException("Foo not found")
}
WHY NULL ISN'T AN OPTION
WHAT'S WRONG WITH NULL
/* 1. Nobody knows about null, not even compiler */
String foo = request.params("foo")
/* 2. Annoying checking */
if (foo != null) {
String bar = request.params("bar")
// if (bar != null) {
/* 3. Danger of infamous NullPointerException,
everbody can forget some check */
doSomething(foo, bar)
// } else {
/* 4. Optionated detailed failures,
sometimes failure in the end is enough */
// throw new ApplicationException("Bar not found")
// }
} else {
/* 5. Design flaw, just original exception replacement */
throw new ApplicationException("Foo not found")
}
DEALING WITH NON-EXISTENCE
DIFFERENT APPROACHES COMPARED
Java relies on sad null
Groovy provides null-safe operator for accessing
properties
Clojure uses nilwhich is okay very often, but sometimes
it leads to an exception higher in call hierarchy
foo?.bar?.baz
GETTING RID OF NULL
NON-EXISTENCE SCALA WAY
Container with one or none element
sealed abstract class Option[A]
case class Some[+A](x: A) extends Option[A]
case object None extends Option[Nothing]
OPTION
1. States that value may or may not be present on type level
2. You are forced by the compiler to deal with it
3. No way to accidentally rely on presence of a value
4. Clearly documents an intention
OPTION IS MANDARORY!
OPTION
CREATING AN OPTION
Never do this
Rather use factory method on companion object
val certain = Some("Sun comes up")
val pitty = None
val nonSense = Some(null)
val muchBetter = Option(null) // Results to None
val certainAgain = Option("Sun comes up") // Some(Sun comes up)
OPTION
WORKING WITH OPTION AN OLD WAY
Don't do this (only in exceptional cases)
// Assume that
def param[String](name: String): Option[String] ...
val fooParam = request.param("foo")
val foo = if (fooParam.isDefined) {
fooParam.get // throws NoSuchElementException when None
} else {
"Default foo" // Default value
}
OPTION
PATTERN MATCHING
Don't do this (there's a better way)
val foo = request.param("foo") match {
case Some(value) => value
case None => "Default foo" // Default value
}
OPTION
PROVIDING A DEFAULT VALUE
Default value is by-name parameter. It's evaluated lazily.
// any long computation for default value
val foo = request.param("foo") getOrElse ("Default foo")
OPTION
TREATING IT FUNCTIONAL WAY
Think of Option as collection
It is biased towards Some
You can map, flatMapor compose Option(s) when it
contains value, i.e. it's Some
OPTION
EXAMPLE
Suppose following model and DAO
case class User(id: Int, name: String, age: Option[Int])
// In domain model, any optional value has to be expressed with Option
object UserDao {
def findById(id: Int): Option[User] = ...
// Id can always be incorrect, e.g. it's possible that user does not
exist already
}
OPTION
SIDE-EFFECTING
Use case: Printing the user name
// Suppose we have an userId from somewhere
val userOpt = UserDao.findById(userId)
// Just print user name
userOpt.foreach { user =>
println(user.name) // Nothing will be printed when None
} // Result is Unit (like void in Java)
// Or more concise
userOpt.foreach( user => println(user) )
// Or even more
userOpt.foreach( println(_) )
userOpt.foreach( println )
OPTION
MAP, FLATMAP & CO.
Use case: Extracting age
// Extracting age
val ageOpt = UserDao.findById(userId).map( _.age )
// Returns Option[Option[Int]]
val ageOpt = UserDao.findById(userId).map( _.age.map( age => age ) )
// ReturnsOption[Option[Int]] too
// Extracting age, take 2
val ageOpt = UserDao.findById(userId).flatMap( _.age.map( age => age )
)
// Returns Option[Int]
OPTION
FOR COMPREHENSIONS
Same use case as before
Usage in left side of generator
// Extracting age, take 3
val ageOpt = for {
user <- UserDao.findById(userId)
age <- user.age
} yield age // Returns Option[Int]
// Extracting age, take 3
val ageOpt = for {
User(_, Some(age)) <- UserDao.findById(userId)
} yield age // Returns Option[Int]
OPTION
COMPOSING TO LIST
Use case: Pretty-print of user
Different notation
Both prints
Rule of thumb: wrap all mandatory fields with Option and
then concatenate with optional ones
def prettyPrint(user: User) =
List(Option(user.name), user.age).mkString(", ")
def prettyPrint(user: User) =
(Option(user.name) ++ user.age).mkString(", ")
val foo = User("Foo", Some(10))
val bar = User("Bar", None)
prettyPrint(foo) // Prints "Foo, 10"
prettyPrint(bar) // Prints "Bar"
Option, Either, Try and what to do with corner cases when they arise
OPTION
CHAINING
Use case: Fetching or creating the user
More appropriate, when Useris desired directly
object UserDao {
// New method
def createUser: User
}
val userOpt = UserDao.findById(userId) orElse Some(UserDao.create)
val user = UserDao.findById(userId) getOrElse UserDao.create
OPTION
MORE TO EXPLORE
sealed abstract class Option[A] {
def fold[B](ifEmpty: Ó B)(f: (A) Ó B): B
def filter(p: (A) Ó Boolean): Option[A]
def exists(p: (A) Ó Boolean): Boolean
...
}
IS OPTION APPROPRIATE?
Consider following piece of code
When something went wrong, cause is lost forever
case class UserFilter(name: String, age: Int)
def parseFilter(input: String): Option[UserFilter] = {
for {
name <- parseName(input)
age <- parseAge(input)
} yield UserFilter(name, age)
}
// Suppose that parseName and parseAge throws FilterException
def parseFilter(input: String): Option[UserFilter]
throws FilterException { ... }
// caller side
val filter = try {
parseFilter(input)
} catch {
case e: FilterException => whatToDoInTheMiddleOfTheCode(e)
}
Exception doesn't help much. It only introduces overhead
INTRODUCING EITHER
Container with disjoint types.
sealed abstract class Either[+L, +R]
case class Left[+L, +R](a: L) extends Either[L, R]
case class Right[+L, +R](b: R) extends Either[L, R]
EITHER
1. States that value is either Left[L]or Right[R], but
never both.
2. No explicit sematics, but by convention Left[L]
represents corner case and Right[R]desired one.
3. Functional way of dealing with alternatives, consider:
4. Again, it clearly documents an intention
def doSomething(): Int throws SomeException
// what is this saying? two possible outcomes
def doSomething(): Either[SomeException, Int]
// more functional only one return value
EITHER IS NOT BIASED
EITHER
CREATING EITHER
There is no Either(...)factory method on companion
object.
def parseAge(input: String): Either[String, Int] = {
try {
Right(input.toInt)
} catch {
case nfe: NumberFormatException => Left("Unable to parse age")
}
}
EITHER
WORKING AN OLD WAY AGAIN
Don't do this (only in exceptional cases)
def parseFilter(input: String): Either[String, ExtendedFilter] = {
val name = parseName(input)
if (name.isRight) {
val age = parseAge(input)
if (age.isRight) {
Right(UserFilter(time, rating))
} else age
} else name
}
EITHER
PATTERN MATCHING
Don't do this (there's a better way)
def parseFilter(input: String): Either[String, ExtendedFilter] = {
parseName(input) match {
case Right(name) => parseAge(input) match {
case Right(age) => UserFilter(name, age)
case error: Left[_] => error
}
case error: Left[_] => error
}
}
EITHER
PROJECTIONS
You cannot directly use instance of Eitheras collection.
It's unbiased, you have to define what is your prefered side.
Working on success, only 1st error is returned.
either.rightreturns RightProjection
def parseFilter(input: String): Either[String, UserFilter] = {
for {
name <- parseName(input).right
age <- parseAge(input).right
} yield Right(UserFilter(name, age))
}
EITHER
PROJECTIONS, TAKE 2
Working on both sides, all errors are collected.
either.leftreturns LeftProjection
def parseFilter(input: String): Either[List[String], UserFilter] = {
val name = parseName(input)
val age = parseAge(input)
val errors = name.left.toOption ++ age.left.toOption
if (errors.isEmpty) {
Right(UserFilter(name.right.get, age.right.get))
} else {
Left(errors)
}
}
EITHER
PROJECTIONS, TAKE 3
Both projection are biased wrappers for Either
You can use map, flatMapon them too, but beware
This is inconsistent in regdard to other collections.
val rightThing = Right(User("Foo", Some(10)))
val projection = rightThing.right // Type is RightProjection[User]
val rightThingAgain = projection.map ( _.name )
// Isn't RightProjection[User] but Right[User]
EITHER
PROJECTIONS, TAKE 4
It can lead to problems with for comprehensions.
This won't compile.
After removing syntactic suggar, we get
We need projection again
for {
name <- parseName(input).right
bigName <- name.capitalize
} yield bigName
parseName(input).right.map { name =>
val bigName = name.capitalize
(bigName)
}.map { case (x) => x } // Map is not member of Either
for {
name <- parseName(input).right
bigName <- Right(name.capitalize).right
} yield bigName
EITHER
FOLDING
Allows transforming the Eitherregardless if it's Rightor
Lefton the same type
Accepts functions, both are evaluated lazily. Result from both
functions has same type.
// Once upon a time in controller
parseFilter(input).fold(
// Bad (Left) side transformation to HttpResponse
errors => BadRequest("Error in filter")
// Good (Right) side transformation to HttpResponse
filter => Ok(doSomethingWith(filter))
)
EITHER
MORE TO EXPLORE
sealed abstract class Either[+A, +B] {
def joinLeft[A1 >: A, B1 >: B, C](implicit ev: <:<[A1, Either[C, B1
]]): Either[C, B1]
def joinRight[A1 >: A, B1 >: B, C](implicit ev: <:<[B1, Either[A1,
C]]): Either[A1, C]
def swap: Product with Serializable with Either[B, A]
}
THROWING AND CATCHING EXCEPTIONS
SOMETIMES THINGS REALLY GO WRONG
You can use classic try/catch/finallyconstruct
def parseAge(input: String): Either[String, Int] = {
try {
Right(input.toInt)
} catch {
case nfe: NumberFormatException => Left("Unable to parse age")
}
}
THROWING AND CATCHING EXCEPTIONS
SOMETIMES THINGS REALLY GO WRONG, TAKE 2
But, it's try/catch/finallyon steroids thanks to pattern
matching
try {
someHorribleCodeHere()
} catch {
// Catching multiple types
case e @ (_: IOException | _: NastyExpception) => cleanUpMess()
// Catching exceptions by message
case e : AnotherNastyException
if e.getMessage contains "Wrong again" => cleanUpMess()
// Catching all exceptions
case e: Exception => cleanUpMess()
}
THROWING AND CATCHING EXCEPTIONS
SOMETIMES THINGS REALLY GO WRONG, TAKE 3
It's powerful, but beware
Never do this!
Prefered approach of catching all
try {
someHorribleCodeHere()
} catch {
// This will match scala.util.control.ControlThrowable too
case _ => cleanUpMess()
}
try {
someHorribleCodeHere()
} catch {
// This will match scala.util.control.ControlThrowable too
case t: ControlThrowable => throw t
case _ => cleanUpMess()
}
Option, Either, Try and what to do with corner cases when they arise
WHAT'S WRONG WITH EXCEPTIONS
1. Referential transparency - is there a value the RHS can be
replaced with? No.
2. Code base can become ugly
3. Exceptions do not go well with concurrency
val something = throw new IllegalArgumentException("Foo is missing")
// Result type is Nothing
SHOULD I THROW AN EXCEPTION?
No, there is better approach
EXCEPTION HANDLING FUNCTIONAL WAY
Please welcome
import scala.util.control._
and
Collection of Throwableor value
sealed trait Try[A]
case class Failure[A](e: Throwable) extends Try[A]
case class Success[A](value: A) extends Try[A]
TRY
1. States that computation may be Success[T]or may be
Failure[T]ending with Throwableon type level
2. Similar to Option, it's Successbiased
3. It's try/catchwithout boilerplate
4. Again it clearly documents what is happening
TRY
LIKE OPTION
All the operations from Optionare present
sealed abstract class Try[+T] {
// Throws exception of Failure or return value of Success
def get: T
// Old way checks
def isFailure: Boolean
def isSuccess: Boolean
// map, flatMap & Co.
def map[U](f: (T) Ó U): Try[U]
def flatMap[U](f: (T) Ó Try[U]): Try[U]
// Side effecting
def foreach[U](f: (T) Ó U): Unit
// Default value
def getOrElse[U >: T](default: Ó U): U
// Chaining
def orElse[U >: T](default: Ó Try[U]): Try[U]
}
TRY
BUT THERE IS MORE
Assume that
Recovering from a Failure
Converting to Option
def parseAge(input: String): Try[Int] = Try ( input.toInt )
val age = parseAge("not a number") recover {
case e: NumberFormatException => 0 // Default value
case _ => -1 // Another default value
} // Result is always Success
val ageOpt = age.toOption
// Will be Some if Success, None if Failure
SCALA.UTIL.CONTROL._
1. Utility methods for common exception handling patterns
2. Less boiler plate than try/catch/finally
SCALA.UTIL.CONTROL._
CATCHING AN EXCEPTION
It returns Catch[T]
catching(classOf[NumberFormatException]) {
input.toInt
} // Returns Catch[Int]
SCALA.UTIL.CONTROL._
CONVERTING
Converting to `Option
Converting to Either
Converting to Try
catching(classOf[NumberFormatException]).opt {
input.toInt
} // Returns Option[Int]
failing(classOf[NumberFormatException]) {
input.toInt
} // Returns Option[Int]
catching(classOf[NumberFormatException]).either {
input.toInt
} // Returns Either[Throwable, Int]
catching(classOf[NumberFormatException]).withTry {
input.toInt
} // Returns Try[Int]
Option, Either, Try and what to do with corner cases when they arise
SCALA.UTIL.CONTROL._
SIDE-EFFECTING
ignoring(classOf[NumberFormatException]) {
println(input.toInt)
} // Returns Catch[Unit]
SCALA.UTIL.CONTROL._
CATCHING NON-FATAL EXCEPTIONS
What are non-fatal exceptions?
All instead of:
VirtualMachineError, ThreadDeath,
InterruptedException, LinkageError,
ControlThrowable, NotImplementedError
nonFatalCatch {
println(input.toInt)
}
SCALA.UTIL.CONTROL._
PROVIDING DEFAULT VALUE
val age = failAsValue(classOf[NumberFormatException])(0) {
input.toInt
}
SCALA.UTIL.CONTROL._
WHAT ABOUT FINALLY
With catch logic
No catch logic
catching(classOf[NumberFormatException]).andFinally {
println("Age parsed somehow")
}.apply {
input.toInt
}
ultimately(println("Age parsed somehow")) {
input.toInt
}
SCALA.UTIL.CONTROL._
There's more to cover and explore,
please check out the .Scala documentation
THANKS FOR YOUR ATTENTION

More Related Content

What's hot (18)

PDF
Java 5 New Feature
xcoda
 
PDF
Swift Programming Language
Giuseppe Arici
 
TXT
Acciones para AmigoBot
jhonsoomelol
 
PDF
Thumbtack Expertise Days # 5 - Javaz
Alexey Remnev
 
PDF
Static types on javascript?! Type checking approaches to ensure healthy appli...
Arthur Puthin
 
PPT
JAVA OOP
Sunil OS
 
PDF
Cocoa Design Patterns in Swift
Michele Titolo
 
PDF
Core csharp and net quick reference
ilesh raval
 
PDF
Peyton jones-2011-type classes
Takayuki Muranushi
 
PDF
Peyton jones-2009-fun with-type_functions-slide
Takayuki Muranushi
 
PPT
JAVA Tutorial- Do's and Don'ts of Java programming
Keshav Kumar
 
PDF
Scala Paradigms
Tom Flaherty
 
KEY
Erjang - A JVM-based Erlang VM
Kresten Krab Thorup
 
PDF
Python
대갑 김
 
PDF
Cheat Sheet java
arkslideshareacc
 
PDF
2 kotlin vs. java: what java has that kotlin does not
Sergey Bandysik
 
KEY
Uses & Abuses of Mocks & Stubs
PatchSpace Ltd
 
PPT
Google collections api an introduction
gosain20
 
Java 5 New Feature
xcoda
 
Swift Programming Language
Giuseppe Arici
 
Acciones para AmigoBot
jhonsoomelol
 
Thumbtack Expertise Days # 5 - Javaz
Alexey Remnev
 
Static types on javascript?! Type checking approaches to ensure healthy appli...
Arthur Puthin
 
JAVA OOP
Sunil OS
 
Cocoa Design Patterns in Swift
Michele Titolo
 
Core csharp and net quick reference
ilesh raval
 
Peyton jones-2011-type classes
Takayuki Muranushi
 
Peyton jones-2009-fun with-type_functions-slide
Takayuki Muranushi
 
JAVA Tutorial- Do's and Don'ts of Java programming
Keshav Kumar
 
Scala Paradigms
Tom Flaherty
 
Erjang - A JVM-based Erlang VM
Kresten Krab Thorup
 
Python
대갑 김
 
Cheat Sheet java
arkslideshareacc
 
2 kotlin vs. java: what java has that kotlin does not
Sergey Bandysik
 
Uses & Abuses of Mocks & Stubs
PatchSpace Ltd
 
Google collections api an introduction
gosain20
 

Similar to Option, Either, Try and what to do with corner cases when they arise (20)

PDF
Beginning-Scala-Options-Either-Try.pdf
OvidiuEremia2
 
PPTX
Intro to Functional Programming in Scala
Shai Yallin
 
PPTX
Scala best practices
Alexander Zaidel
 
PDF
Event Sourcing and Functional Programming
GlobalLogic Ukraine
 
PDF
Functional Programming & Event Sourcing - a pair made in heaven
Pawel Szulc
 
ODP
Functional programming with Scala
Neelkanth Sachdeva
 
PDF
Scala or functional programming from a python developer's perspective
gabalese
 
PDF
Meet scala
Wojciech Pituła
 
PDF
A Scala tutorial
Dima Statz
 
PDF
Generic Functional Programming with Type Classes
Tapio Rautonen
 
PDF
Demystifying functional programming with Scala
Denis
 
PDF
From Java to Scala - advantages and possible risks
SeniorDevOnly
 
PDF
Scala vs Java 8 in a Java 8 World
BTI360
 
PDF
Hope driven development
Andrew Gustafson
 
ODP
Functional Programming With Scala
Knoldus Inc.
 
KEY
Building a Mongo DSL in Scala at Hot Potato
MongoDB
 
PPTX
Scala for curious
Tim (dev-tim) Zadorozhniy
 
PDF
The what over the how (another way on android development with kotlin)
Jose Manuel Pereira Garcia
 
PDF
Scala UA: Big Step To Functional Programming
Alex Fruzenshtein
 
PDF
Exploring ZIO Prelude: The game changer for typeclasses in Scala
Jorge Vásquez
 
Beginning-Scala-Options-Either-Try.pdf
OvidiuEremia2
 
Intro to Functional Programming in Scala
Shai Yallin
 
Scala best practices
Alexander Zaidel
 
Event Sourcing and Functional Programming
GlobalLogic Ukraine
 
Functional Programming & Event Sourcing - a pair made in heaven
Pawel Szulc
 
Functional programming with Scala
Neelkanth Sachdeva
 
Scala or functional programming from a python developer's perspective
gabalese
 
Meet scala
Wojciech Pituła
 
A Scala tutorial
Dima Statz
 
Generic Functional Programming with Type Classes
Tapio Rautonen
 
Demystifying functional programming with Scala
Denis
 
From Java to Scala - advantages and possible risks
SeniorDevOnly
 
Scala vs Java 8 in a Java 8 World
BTI360
 
Hope driven development
Andrew Gustafson
 
Functional Programming With Scala
Knoldus Inc.
 
Building a Mongo DSL in Scala at Hot Potato
MongoDB
 
Scala for curious
Tim (dev-tim) Zadorozhniy
 
The what over the how (another way on android development with kotlin)
Jose Manuel Pereira Garcia
 
Scala UA: Big Step To Functional Programming
Alex Fruzenshtein
 
Exploring ZIO Prelude: The game changer for typeclasses in Scala
Jorge Vásquez
 
Ad

More from Michal Bigos (6)

PDF
All About ... Functions
Michal Bigos
 
PDF
Scala eXchange 2013 Report
Michal Bigos
 
PDF
Functional Domain Modeling
Michal Bigos
 
PDF
SBT Crash Course
Michal Bigos
 
PDF
Dependency injection in scala
Michal Bigos
 
PDF
Integration Testing With ScalaTest and MongoDB
Michal Bigos
 
All About ... Functions
Michal Bigos
 
Scala eXchange 2013 Report
Michal Bigos
 
Functional Domain Modeling
Michal Bigos
 
SBT Crash Course
Michal Bigos
 
Dependency injection in scala
Michal Bigos
 
Integration Testing With ScalaTest and MongoDB
Michal Bigos
 
Ad

Recently uploaded (20)

PPTX
BANDHA (BANDAGES) PPT.pptx ayurveda shalya tantra
rakhan78619
 
PPTX
How to Configure Lost Reasons in Odoo 18 CRM
Celine George
 
PDF
IMP NAAC-Reforms-Stakeholder-Consultation-Presentation-on-Draft-Metrics-Unive...
BHARTIWADEKAR
 
PPT
Talk on Critical Theory, Part One, Philosophy of Social Sciences
Soraj Hongladarom
 
PPTX
How to Manage Promotions in Odoo 18 Sales
Celine George
 
PDF
CONCURSO DE POESIA “POETUFAS – PASSOS SUAVES PELO VERSO.pdf
Colégio Santa Teresinha
 
PPTX
How to Configure Access Rights of Manufacturing Orders in Odoo 18 Manufacturing
Celine George
 
PDF
IMP NAAC REFORMS 2024 - 10 Attributes.pdf
BHARTIWADEKAR
 
PPTX
Stereochemistry-Optical Isomerism in organic compoundsptx
Tarannum Nadaf-Mansuri
 
PPTX
Soil and agriculture microbiology .pptx
Keerthana Ramesh
 
PPSX
HEALTH ASSESSMENT (Community Health Nursing) - GNM 1st Year
Priyanshu Anand
 
PPSX
Health Planning in india - Unit 03 - CHN 2 - GNM 3RD YEAR.ppsx
Priyanshu Anand
 
PPTX
HYDROCEPHALUS: NURSING MANAGEMENT .pptx
PRADEEP ABOTHU
 
PPTX
Capitol Doctoral Presentation -July 2025.pptx
CapitolTechU
 
PPTX
ASRB NET 2023 PREVIOUS YEAR QUESTION PAPER GENETICS AND PLANT BREEDING BY SAT...
Krashi Coaching
 
PPTX
Views on Education of Indian Thinkers J.Krishnamurthy..pptx
ShrutiMahanta1
 
PPTX
2025 Winter SWAYAM NPTEL & A Student.pptx
Utsav Yagnik
 
PPTX
How to Create Rental Orders in Odoo 18 Rental
Celine George
 
PPTX
STAFF DEVELOPMENT AND WELFARE: MANAGEMENT
PRADEEP ABOTHU
 
PPTX
Quarter1-English3-W4-Identifying Elements of the Story
FLORRACHELSANTOS
 
BANDHA (BANDAGES) PPT.pptx ayurveda shalya tantra
rakhan78619
 
How to Configure Lost Reasons in Odoo 18 CRM
Celine George
 
IMP NAAC-Reforms-Stakeholder-Consultation-Presentation-on-Draft-Metrics-Unive...
BHARTIWADEKAR
 
Talk on Critical Theory, Part One, Philosophy of Social Sciences
Soraj Hongladarom
 
How to Manage Promotions in Odoo 18 Sales
Celine George
 
CONCURSO DE POESIA “POETUFAS – PASSOS SUAVES PELO VERSO.pdf
Colégio Santa Teresinha
 
How to Configure Access Rights of Manufacturing Orders in Odoo 18 Manufacturing
Celine George
 
IMP NAAC REFORMS 2024 - 10 Attributes.pdf
BHARTIWADEKAR
 
Stereochemistry-Optical Isomerism in organic compoundsptx
Tarannum Nadaf-Mansuri
 
Soil and agriculture microbiology .pptx
Keerthana Ramesh
 
HEALTH ASSESSMENT (Community Health Nursing) - GNM 1st Year
Priyanshu Anand
 
Health Planning in india - Unit 03 - CHN 2 - GNM 3RD YEAR.ppsx
Priyanshu Anand
 
HYDROCEPHALUS: NURSING MANAGEMENT .pptx
PRADEEP ABOTHU
 
Capitol Doctoral Presentation -July 2025.pptx
CapitolTechU
 
ASRB NET 2023 PREVIOUS YEAR QUESTION PAPER GENETICS AND PLANT BREEDING BY SAT...
Krashi Coaching
 
Views on Education of Indian Thinkers J.Krishnamurthy..pptx
ShrutiMahanta1
 
2025 Winter SWAYAM NPTEL & A Student.pptx
Utsav Yagnik
 
How to Create Rental Orders in Odoo 18 Rental
Celine George
 
STAFF DEVELOPMENT AND WELFARE: MANAGEMENT
PRADEEP ABOTHU
 
Quarter1-English3-W4-Identifying Elements of the Story
FLORRACHELSANTOS
 

Option, Either, Try and what to do with corner cases when they arise

  • 1. OPTION, EITHER, TRY AND WHAT TO DO WITH CORNER CASES WHEN THEY ARISE KNOW YOUR LIBRARY MINI-SERIES By /Michal Bigos @teliatko
  • 2. KNOW YOUR LIBRARY - MINI SERIES 1. Hands-on with types from Scala library 2. DO's and DON'Ts 3. Intended for rookies, but Scala basic syntax assumed 4. Real world use cases
  • 3. WHY NULL ISN'T AN OPTION CONSIDER FOLLOWING CODE String foo = request.params("foo") if (foo != null) { String bar = request.params("bar") if (bar != null) { doSomething(foo, bar) } else { throw new ApplicationException("Bar not found") } } else { throw new ApplicationException("Foo not found") }
  • 4. WHY NULL ISN'T AN OPTION WHAT'S WRONG WITH NULL /* 1. Nobody knows about null, not even compiler */ String foo = request.params("foo") /* 2. Annoying checking */ if (foo != null) { String bar = request.params("bar") // if (bar != null) { /* 3. Danger of infamous NullPointerException, everbody can forget some check */ doSomething(foo, bar) // } else { /* 4. Optionated detailed failures, sometimes failure in the end is enough */ // throw new ApplicationException("Bar not found") // } } else { /* 5. Design flaw, just original exception replacement */ throw new ApplicationException("Foo not found") }
  • 5. DEALING WITH NON-EXISTENCE DIFFERENT APPROACHES COMPARED Java relies on sad null Groovy provides null-safe operator for accessing properties Clojure uses nilwhich is okay very often, but sometimes it leads to an exception higher in call hierarchy foo?.bar?.baz
  • 6. GETTING RID OF NULL NON-EXISTENCE SCALA WAY Container with one or none element sealed abstract class Option[A] case class Some[+A](x: A) extends Option[A] case object None extends Option[Nothing]
  • 7. OPTION 1. States that value may or may not be present on type level 2. You are forced by the compiler to deal with it 3. No way to accidentally rely on presence of a value 4. Clearly documents an intention
  • 9. OPTION CREATING AN OPTION Never do this Rather use factory method on companion object val certain = Some("Sun comes up") val pitty = None val nonSense = Some(null) val muchBetter = Option(null) // Results to None val certainAgain = Option("Sun comes up") // Some(Sun comes up)
  • 10. OPTION WORKING WITH OPTION AN OLD WAY Don't do this (only in exceptional cases) // Assume that def param[String](name: String): Option[String] ... val fooParam = request.param("foo") val foo = if (fooParam.isDefined) { fooParam.get // throws NoSuchElementException when None } else { "Default foo" // Default value }
  • 11. OPTION PATTERN MATCHING Don't do this (there's a better way) val foo = request.param("foo") match { case Some(value) => value case None => "Default foo" // Default value }
  • 12. OPTION PROVIDING A DEFAULT VALUE Default value is by-name parameter. It's evaluated lazily. // any long computation for default value val foo = request.param("foo") getOrElse ("Default foo")
  • 13. OPTION TREATING IT FUNCTIONAL WAY Think of Option as collection It is biased towards Some You can map, flatMapor compose Option(s) when it contains value, i.e. it's Some
  • 14. OPTION EXAMPLE Suppose following model and DAO case class User(id: Int, name: String, age: Option[Int]) // In domain model, any optional value has to be expressed with Option object UserDao { def findById(id: Int): Option[User] = ... // Id can always be incorrect, e.g. it's possible that user does not exist already }
  • 15. OPTION SIDE-EFFECTING Use case: Printing the user name // Suppose we have an userId from somewhere val userOpt = UserDao.findById(userId) // Just print user name userOpt.foreach { user => println(user.name) // Nothing will be printed when None } // Result is Unit (like void in Java) // Or more concise userOpt.foreach( user => println(user) ) // Or even more userOpt.foreach( println(_) ) userOpt.foreach( println )
  • 16. OPTION MAP, FLATMAP & CO. Use case: Extracting age // Extracting age val ageOpt = UserDao.findById(userId).map( _.age ) // Returns Option[Option[Int]] val ageOpt = UserDao.findById(userId).map( _.age.map( age => age ) ) // ReturnsOption[Option[Int]] too // Extracting age, take 2 val ageOpt = UserDao.findById(userId).flatMap( _.age.map( age => age ) ) // Returns Option[Int]
  • 17. OPTION FOR COMPREHENSIONS Same use case as before Usage in left side of generator // Extracting age, take 3 val ageOpt = for { user <- UserDao.findById(userId) age <- user.age } yield age // Returns Option[Int] // Extracting age, take 3 val ageOpt = for { User(_, Some(age)) <- UserDao.findById(userId) } yield age // Returns Option[Int]
  • 18. OPTION COMPOSING TO LIST Use case: Pretty-print of user Different notation Both prints Rule of thumb: wrap all mandatory fields with Option and then concatenate with optional ones def prettyPrint(user: User) = List(Option(user.name), user.age).mkString(", ") def prettyPrint(user: User) = (Option(user.name) ++ user.age).mkString(", ") val foo = User("Foo", Some(10)) val bar = User("Bar", None) prettyPrint(foo) // Prints "Foo, 10" prettyPrint(bar) // Prints "Bar"
  • 20. OPTION CHAINING Use case: Fetching or creating the user More appropriate, when Useris desired directly object UserDao { // New method def createUser: User } val userOpt = UserDao.findById(userId) orElse Some(UserDao.create) val user = UserDao.findById(userId) getOrElse UserDao.create
  • 21. OPTION MORE TO EXPLORE sealed abstract class Option[A] { def fold[B](ifEmpty: Ó B)(f: (A) Ó B): B def filter(p: (A) Ó Boolean): Option[A] def exists(p: (A) Ó Boolean): Boolean ... }
  • 22. IS OPTION APPROPRIATE? Consider following piece of code When something went wrong, cause is lost forever case class UserFilter(name: String, age: Int) def parseFilter(input: String): Option[UserFilter] = { for { name <- parseName(input) age <- parseAge(input) } yield UserFilter(name, age) } // Suppose that parseName and parseAge throws FilterException def parseFilter(input: String): Option[UserFilter] throws FilterException { ... } // caller side val filter = try { parseFilter(input) } catch { case e: FilterException => whatToDoInTheMiddleOfTheCode(e) }
  • 23. Exception doesn't help much. It only introduces overhead
  • 24. INTRODUCING EITHER Container with disjoint types. sealed abstract class Either[+L, +R] case class Left[+L, +R](a: L) extends Either[L, R] case class Right[+L, +R](b: R) extends Either[L, R]
  • 25. EITHER 1. States that value is either Left[L]or Right[R], but never both. 2. No explicit sematics, but by convention Left[L] represents corner case and Right[R]desired one. 3. Functional way of dealing with alternatives, consider: 4. Again, it clearly documents an intention def doSomething(): Int throws SomeException // what is this saying? two possible outcomes def doSomething(): Either[SomeException, Int] // more functional only one return value
  • 26. EITHER IS NOT BIASED
  • 27. EITHER CREATING EITHER There is no Either(...)factory method on companion object. def parseAge(input: String): Either[String, Int] = { try { Right(input.toInt) } catch { case nfe: NumberFormatException => Left("Unable to parse age") } }
  • 28. EITHER WORKING AN OLD WAY AGAIN Don't do this (only in exceptional cases) def parseFilter(input: String): Either[String, ExtendedFilter] = { val name = parseName(input) if (name.isRight) { val age = parseAge(input) if (age.isRight) { Right(UserFilter(time, rating)) } else age } else name }
  • 29. EITHER PATTERN MATCHING Don't do this (there's a better way) def parseFilter(input: String): Either[String, ExtendedFilter] = { parseName(input) match { case Right(name) => parseAge(input) match { case Right(age) => UserFilter(name, age) case error: Left[_] => error } case error: Left[_] => error } }
  • 30. EITHER PROJECTIONS You cannot directly use instance of Eitheras collection. It's unbiased, you have to define what is your prefered side. Working on success, only 1st error is returned. either.rightreturns RightProjection def parseFilter(input: String): Either[String, UserFilter] = { for { name <- parseName(input).right age <- parseAge(input).right } yield Right(UserFilter(name, age)) }
  • 31. EITHER PROJECTIONS, TAKE 2 Working on both sides, all errors are collected. either.leftreturns LeftProjection def parseFilter(input: String): Either[List[String], UserFilter] = { val name = parseName(input) val age = parseAge(input) val errors = name.left.toOption ++ age.left.toOption if (errors.isEmpty) { Right(UserFilter(name.right.get, age.right.get)) } else { Left(errors) } }
  • 32. EITHER PROJECTIONS, TAKE 3 Both projection are biased wrappers for Either You can use map, flatMapon them too, but beware This is inconsistent in regdard to other collections. val rightThing = Right(User("Foo", Some(10))) val projection = rightThing.right // Type is RightProjection[User] val rightThingAgain = projection.map ( _.name ) // Isn't RightProjection[User] but Right[User]
  • 33. EITHER PROJECTIONS, TAKE 4 It can lead to problems with for comprehensions. This won't compile. After removing syntactic suggar, we get We need projection again for { name <- parseName(input).right bigName <- name.capitalize } yield bigName parseName(input).right.map { name => val bigName = name.capitalize (bigName) }.map { case (x) => x } // Map is not member of Either
  • 34. for { name <- parseName(input).right bigName <- Right(name.capitalize).right } yield bigName
  • 35. EITHER FOLDING Allows transforming the Eitherregardless if it's Rightor Lefton the same type Accepts functions, both are evaluated lazily. Result from both functions has same type. // Once upon a time in controller parseFilter(input).fold( // Bad (Left) side transformation to HttpResponse errors => BadRequest("Error in filter") // Good (Right) side transformation to HttpResponse filter => Ok(doSomethingWith(filter)) )
  • 36. EITHER MORE TO EXPLORE sealed abstract class Either[+A, +B] { def joinLeft[A1 >: A, B1 >: B, C](implicit ev: <:<[A1, Either[C, B1 ]]): Either[C, B1] def joinRight[A1 >: A, B1 >: B, C](implicit ev: <:<[B1, Either[A1, C]]): Either[A1, C] def swap: Product with Serializable with Either[B, A] }
  • 37. THROWING AND CATCHING EXCEPTIONS SOMETIMES THINGS REALLY GO WRONG You can use classic try/catch/finallyconstruct def parseAge(input: String): Either[String, Int] = { try { Right(input.toInt) } catch { case nfe: NumberFormatException => Left("Unable to parse age") } }
  • 38. THROWING AND CATCHING EXCEPTIONS SOMETIMES THINGS REALLY GO WRONG, TAKE 2 But, it's try/catch/finallyon steroids thanks to pattern matching try { someHorribleCodeHere() } catch { // Catching multiple types case e @ (_: IOException | _: NastyExpception) => cleanUpMess() // Catching exceptions by message case e : AnotherNastyException if e.getMessage contains "Wrong again" => cleanUpMess() // Catching all exceptions case e: Exception => cleanUpMess() }
  • 39. THROWING AND CATCHING EXCEPTIONS SOMETIMES THINGS REALLY GO WRONG, TAKE 3 It's powerful, but beware Never do this! Prefered approach of catching all try { someHorribleCodeHere() } catch { // This will match scala.util.control.ControlThrowable too case _ => cleanUpMess() } try { someHorribleCodeHere() } catch { // This will match scala.util.control.ControlThrowable too case t: ControlThrowable => throw t case _ => cleanUpMess() }
  • 41. WHAT'S WRONG WITH EXCEPTIONS 1. Referential transparency - is there a value the RHS can be replaced with? No. 2. Code base can become ugly 3. Exceptions do not go well with concurrency val something = throw new IllegalArgumentException("Foo is missing") // Result type is Nothing
  • 42. SHOULD I THROW AN EXCEPTION? No, there is better approach
  • 43. EXCEPTION HANDLING FUNCTIONAL WAY Please welcome import scala.util.control._ and Collection of Throwableor value sealed trait Try[A] case class Failure[A](e: Throwable) extends Try[A] case class Success[A](value: A) extends Try[A]
  • 44. TRY 1. States that computation may be Success[T]or may be Failure[T]ending with Throwableon type level 2. Similar to Option, it's Successbiased 3. It's try/catchwithout boilerplate 4. Again it clearly documents what is happening
  • 45. TRY LIKE OPTION All the operations from Optionare present sealed abstract class Try[+T] { // Throws exception of Failure or return value of Success def get: T // Old way checks def isFailure: Boolean def isSuccess: Boolean // map, flatMap & Co. def map[U](f: (T) Ó U): Try[U] def flatMap[U](f: (T) Ó Try[U]): Try[U] // Side effecting def foreach[U](f: (T) Ó U): Unit // Default value def getOrElse[U >: T](default: Ó U): U // Chaining def orElse[U >: T](default: Ó Try[U]): Try[U] }
  • 46. TRY BUT THERE IS MORE Assume that Recovering from a Failure Converting to Option def parseAge(input: String): Try[Int] = Try ( input.toInt ) val age = parseAge("not a number") recover { case e: NumberFormatException => 0 // Default value case _ => -1 // Another default value } // Result is always Success val ageOpt = age.toOption // Will be Some if Success, None if Failure
  • 47. SCALA.UTIL.CONTROL._ 1. Utility methods for common exception handling patterns 2. Less boiler plate than try/catch/finally
  • 48. SCALA.UTIL.CONTROL._ CATCHING AN EXCEPTION It returns Catch[T] catching(classOf[NumberFormatException]) { input.toInt } // Returns Catch[Int]
  • 49. SCALA.UTIL.CONTROL._ CONVERTING Converting to `Option Converting to Either Converting to Try catching(classOf[NumberFormatException]).opt { input.toInt } // Returns Option[Int] failing(classOf[NumberFormatException]) { input.toInt } // Returns Option[Int] catching(classOf[NumberFormatException]).either { input.toInt } // Returns Either[Throwable, Int] catching(classOf[NumberFormatException]).withTry { input.toInt } // Returns Try[Int]
  • 52. SCALA.UTIL.CONTROL._ CATCHING NON-FATAL EXCEPTIONS What are non-fatal exceptions? All instead of: VirtualMachineError, ThreadDeath, InterruptedException, LinkageError, ControlThrowable, NotImplementedError nonFatalCatch { println(input.toInt) }
  • 53. SCALA.UTIL.CONTROL._ PROVIDING DEFAULT VALUE val age = failAsValue(classOf[NumberFormatException])(0) { input.toInt }
  • 54. SCALA.UTIL.CONTROL._ WHAT ABOUT FINALLY With catch logic No catch logic catching(classOf[NumberFormatException]).andFinally { println("Age parsed somehow") }.apply { input.toInt } ultimately(println("Age parsed somehow")) { input.toInt }
  • 55. SCALA.UTIL.CONTROL._ There's more to cover and explore, please check out the .Scala documentation
  • 56. THANKS FOR YOUR ATTENTION