Functional Way of Handling Errors in Scala: Option and Either

Reading Time: 4 minutes

In Scala, we all know that throwing an exception is a side effect and there is no such word as “side effect” in functional programming. If exceptions aren’t used in functional code, what is used instead? Now the idea is that in order to implement error handling in a functional style of code we can represent failures and exceptions with ordinary values.

And also, we can write higher-order functions that abstract out common patterns of error handling and recovery.
The functional solution, which consists of returning errors as values, is more secure and retains referential transparency, and through the use of higher-order functions, we can preserve the main advantage of exceptions.

Good and Bad Aspect of Exceptions

When we are dealing with exceptions they often break referential transparency and why is that a problem let’s take an example to understand it

def failingFunc(a: Int): Int = {
   val y: Int = throw new Exception("fail!")
     val x = 43 + 5
     x + y
   catch {case e: Exception => 43}

In the above code, we can prove that y is not referentially transparent if we replace throw new Exception("fail!") our code will produce a different result since the exception will now be raised inside a try block that will catch the exception and return 43:

def failingFunc2(b: Int): Int = {
try {
val x = 42 + 5
x + ((throw new Exception("fail!")): Int)
catch { case e: Exception => 43 }

From above we can say that there are two problems with exceptions:

  • Exception breaks referential transparency and introduce context dependence which is the source of folklore advice that exceptions should be used only for error handling, not for control flow.
  • Exceptions are not type-safe. The type of failingFunc, Int => Int tells us nothing about the fact that there might be an exceptions and the compiler will not force callers of of failingFunc to make a decision about how to handle those exceptions.

So What’s the solution?

The Option data type

One solution is to mention explicitly in the return type that a function may not always have an answer. We introduce a new type, Option.

sealed trait Option[+A]
case class Some[+A](get: A) extends Option[A]
case object None extends Option[Nothing]

Option come up with two cases: If it’s defined or have some sort of value, in that case, it will be a Some, or it can be undefined, in which case it will be None

Let’s take an example:

def mean(xs: Seq[Double]): Option[Double] =
if (xs.isEmpty) None
else Some(xs.sum / xs.length)

In the above code, the return type shows the possibility that the result may not always be defined. We will always be able to retrieve a result of the declared type (Option[Double]) from our function, so the mean is now a total function.

It takes each value of the input type to exactly one value of the output type hence total function.

Basic Functions on Option

The Option is like a List that can contain at most one element and many of the List functions and let’s define them inside a trait

trait Option[+A] {

def map[B](f: A => B): Option[B]                //Apply f if the Option is not None 

def flatMap[B](f: A => Option[B]): Option[B]   //Apply f , which may fail, to Option if not None

def getOrElse[B >: A](default: => B): B        //B >: A says that B type parameter must be a supertype of A

def orElse[B >: A](ob: => Option[B]): Option[B] //Don't evaluate ob unless needed

def filter(f: A => Boolean): Option[A]          //Convert Some to None if the value doesn't satisfy.


Usage Scenarios for Basic Options Type

Although we can use the explicit pattern match on an Option, we’ll almost always use higher-order functions as this style is always preferred cause it’s more functional

Now suppose you have an Employee directory and you have to develop a logic to find a particular employee with its name and along with department.

case class Employee(name: String, department: String)
def lookupByName(name: String): Option[Employee] = ...
val dept: String =
filter(_ != "Accounting").
getOrElse("Default Dept")

The Either data type

The Option isn’t the only data type we could use and also with Option we cannot determine what kind of error has occurred.

There’s a data type that encodes information about whatever information we want about failures and that is Either.

sealed trait Either[+E, +A]
case class Left[+E](value: E) extends Either[E, Nothing]
case class Right[+A](value: A) extends Either[Nothing, A]

Either has only two cases, just like Option. The essential difference is that both cases carry a value. Let’s see an example

def mean(xs: IndexedSeq[Double]): Either[String, Double] =
if (xs.isEmpty)
Left("mean of empty list!")
Right(xs.sum / xs.length)

Basic Functions on Either

Either has the same functions as Options.

trait Either[+E, +A] {
def map[B](f: A => B): Either[E, B]

def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B]

def orElse[EE >: E,B >: A](b: => Either[EE, B]): Either[EE, B]

def map2[EE >: E, B, C](b: Either[EE, B])(f: (A, B) => C): Either[EE, C]


Coding Example of Either

Using Either to validate data

case class Person(name: Name, age: Age)
sealed class Name(val value: String)
sealed class Age(val value: Int)

def mkName(name: String): Either[String, Name] =
if (name == "" || name == null) Left("Name is empty.")
else Right(new Name(name))

def mkAge(age: Int): Either[String, Age] =
if (age < 0) Left("Age is out of range.")
else Right(new Age(age))

def mkPerson(name: String, age: Int): Either[String, Person] =
mkName(name).map2(mkAge(age))(Person(_, _))


Thank you guys for making it to the end of the blog I hope you gained some knowledge on how you can implement Option and Either. If you liked my blog please do check out my more blogs on scala click here.


For getting more knowledge regarding Option and Either I recommend you to refer to the following link:

Written by 

Hi community, I am Raviyanshu from Dehradun a tech enthusiastic trying to make something useful with the help of 0 & 1.