Functional Error Handling in Scala

Scala Extractors

In Java, exceptions are handled using try/catch where we throw exceptions for the failure condition. That’s ok with Java but as a Scala developer, we should avoid this.

Let me take an example,

case class Person(name: String, location: String)
val person = Person("Akhil", "Delhi")

def getPerson(person: Person) : Person = {
  
  if(person.location.equals("Delhi")) {
    person
  }
  else {
    throw new Exception("Invalid Person Object")
  }
}

val requiredPerson = getPerson(person)

In the above example, you can see a getPerson method which takes in a person object and returns in a Person object. This method checks whether the person lives in Delhi or not. If yes then it returns the person object back otherwise it throws an exception. The code seems alright, but we are breaking functional purity. We are not returning the same type always. As a Scala developer, we should choose the functional approach. Scala provides functional error handling to keep functional purity intact.

Functional Error handling

We have few techniques to handle exceptions in scala:

1) Option

Options handle both sides of the coin i.e both positive and negative. It uses Some(value) for the positive case and None for the negative case.

So we can use options in our example:

case class Person(name: String, location: String)

val person = Person("Akhil", "Delhi")
def getPerson(person: Person): Option[Person] = {

  if(person.location.equals("Delhi")) {
    Some(person)
  }
  else {
    None
  }
}

val requiredPerson = getPerson(person)  //Output:requiredPerson: Option[Person] = Some(Person(Akhil,Delhi))

The return type is same for both the scenarios i.e option type.

2) Try

Try is another technique to achieve functional purity. Try results in Success(value) or Failure(exception). Try is ideal when we are dealing with third-party libraries.

In our case let’s take the same example.

import scala.util.Try

case class Person(name: String, location: String)

val person = Person("Akhil", "Delhi")
def getPerson(person: Person): Try[Person] = {

  Try {
    if (person.location.equals("Delhi")) {
      person
    }
    else {
      throw new Exception("Invalid Person Object")
    }
  }
}

val requiredPerson = getPerson(person) //Output:requiredPerson: scala.util.Try[Person] = Success(Person(Akhil,Delhi))

3) Either

Either also handles both cases as other techniques do but it is better than Option and Try. Option has a demerit that it does not provide the error message in case of failures, it just returns None for the failures. Try provides us a failure message for a failure but still, we need to throw an exception which we want to avoid. Either is a good approach to avoid these. It returns Left or Right. Left contains the error message that we pass in our code and Right contains the value itself.

case class Person(name: String, location: String)
case class ErrorMessage(message: String)

val person = Person("Akhil", "Delhi")
def getPerson(person: Person): Either[ErrorMessage, Person] = {

  if(person.location.equals("Delhi")) {
    Right(person)
  }
  else {
    Left(ErrorMessage("Invalid Person Object"))
  }
}

val requiredPerson = getPerson(person)  //Output: requiredPerson: Either[ErrorMessage,Person] = Right(Person(Akhil,Delhi))

 

4) Third Party Libraries

There are many third-party libraries that can also be used to achieve the same but it is out of the scope of this blog.

Thanks for your patience.

Reference:

longcao.org


knoldus-advt-sticker

Written by 

I am a Software Consultant at Knoldus Inc. I am a Scala Enthusiast. I am familiar with Object Oriented Programming Paradigms, and has also worked upon .NET based technologies. Aside from being a programmer, I am familiar with NoSQL database technologies such like Cassandra. I also worked on Lagom microservice architecture.

Leave a Reply

%d bloggers like this: