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: