Error handling in Scala: What, where and how?


The honest way of Handling an errors is to making it to the end user and telling the exact thing that has been happened over failures. Sometimes we can’t afford longer debugging sessions in case of mission critical things. The following phrases won’t help much in debugging when seen by users:

“Blah” went wrong!! (“What?
Your Transaction can’t be complete !! (“why ?
Not possible … (“why?”
Engine did not start… (“why?
Blah.. Blah..

Should not the above phrases have been informative. E.g. “Engine did not start because the Pressure tolerance is 0.05 exceeding to safe pressure tolerance 0.0005”

In this blog, we will talk about some monadic constructs in Scala and other scala based library which saves a lot of debugging sessions when it comes to failure. In this blog,  how we would choose between them according to our need.

An Easy Example?

Let us take the example of validation. We always require at some point of our computation to validate input data. Suppose we have to take an address and pass it further if it is valid. Consider the following example:

def validateStreetAdd(street:String): Option[String] =
    if(street.nonEmpty && street.matches("some valid address")) Some(street) else None

Is “Option” a real option for the use case?

It is designed for a super simple use case. Sometimes you have some value and sometimes you have nothing. Suppose the above method is called with a value “some invalid address”

Here we’ve got following things not correct with our Code:
Unfortunately we can’t use None to process it further until we have a valid address.

When there is no error or backward communication, the users assumed that they had their “thing” done by typing an invalid address: A miscommunication.

The Ideal improvement at the moment would be the following modification of the current version:

def validateStreetAddressWithDefault(street:String): Option[String] =
    if(street.nonEmpty && street.matches("some valid address")) Some(street) else Some("Some Default Address")

Or If you don’t want to do this, you can call the previous version with a .getOrElse(“Default Value”). In fact, latter is the better option, because you have the flexibility to choose the Default value rather than hardcoding it as part of method. Now we are somewhat removed the first problem here but passing a default address for all of the customers would be the last option the users will agree upon. They still want to know what is the exact reason for the failure or What is not correct from the input.

Let’s Try to return the error.

Try[T] is another construct to capture the success or a failure scenarios. It returns a value in both cases. Put any expression in Try and it will return Success[T] if the expression is successfully evaluated and will return Failure[T] in the other case meaning you are allowed to return the exception as a value. However with one restriction that it in case of failures it will only return Throwable types:

def validateZipCode(zipCode:String): Try[Int] = Try(zipCode.toInt)

But Throwing an exception doesn’t make much sense here since it is not much of a calculation. Although we can take this example to understand the use case. If the given string is not a number, it will be a failure. The value from the Try can be extracted in same as Option. It can be matched

validateZipCode("123456") match { case Success(zip) => zip ; case Failure(e) => 100001 }

The above can be replaced with below:
validateZipCode(“123456”).getOrElse(100001)
Try can be mapped over its values:

validateZipCode("123456").map( _ .someOperation)

What if we don’t have to throw an Exception but return something else?

If you don’t want always to throw an exception in failure cases and make an exception/Error of your own, you can use scala.util.Either[A,B]. It gives you two type options A and B to return any information you’ve computed. Do not confuse it with tuple. It will only let use one at a time i.e. either scala.util.Left[A] or scala.util.Right[B]. Look at the enhanced version of zip code validation method:

case class ValidationError(msg:String)

def validateZipCode(zipCode:String): Either[ValidationError, String] =
 (zipCode.matches("^[0-9]+$"), zipCode.length == 6) match {
   case (true,true) => Right(zipCode)
   case (false,true) => Left(ValidationError("Zip Code should be Number"))
   case (true,false) => Left(ValidationError("Zip Code should be exactly 6 digits"))
   case _ => Left(ValidationError("Zip Code should be exactly 6 digits"))
 }

The Output:

scala> validateZipCode("1123")
res0: Either[ValidationError,String] = Left(ValidationError(Zip Code should be exactly 6 digits))
scala> validateZipCode("invalid")
res1: Either[ValidationError,String] = Left(ValidationError(Zip Code should be exactly 6 digits))
scala> validateZipCode("123456")
res2: Either[ValidationError,String] = Right(123456)

By semantics, If you’ve declared the return type Either[A,B] then you are bound to wrap type A in Left and type B in the Right. Going by the scala convention usually you should Project your “Good” values in the Right and “Failures/Errors/Exceptions” in the Left. But don’t worry if you are left handed person! You can always do

def validateZipCode(zipCode:String): Either[ String, ValidationError]

What if convention makes you efficient?

There is another construct called Or which is simpler and more powerful than Either in the Scala. The Or is from the Scalactic library. It uses infix Notation as opposed to Either.
Or has a very good values vs error projection. Either it will return a value wrapped in Good or anything which is not Good is Bad. Bad will wrap whatever has been declared as part of return type in Or. Look at the enhanced version of the validateZipCodeWithOr method below:

def validateZipCodeWithOr(zipCode:String): String Or ValidationError = {
 (zipCode.matches("^[0-9]+$"), zipCode.length == 6) match {
   case (true, true) => Good(zipCode)
   case (false, true) => Bad(ValidationError("Zip Code should be Number"))
   case (true, false) => Bad(ValidationError("Zip Code should be exactly 6 digits"))
   case _ => Bad(ValidationError("Zip Code should be exactly 6 digits"))
 }
}

Here is how I used it:

scala> validateZipCodeWithOr(“110020”)
res0: Good(110020)
scala> validateZipCodeWithOr(“Hello”)
scala: Bad(ValidationError(Zip Code should be exactly 6 digits))

Collecting Errors:

Sometimes giving a detailed error report after all inputs is very useful and saves a lot of efforts for users. Every in Scalactic library used to accumulate all the errors which can be summarized as a report:

Accumulating errors with Or

I could not resist the use of Copy and paste from Scalactic library to make you understand the use of Or for collecting Errors.

“Another difference between Or and Either is that Or enables you to accumulate errors if the Bad type is an Every. An Every is similar to a Seq in that it contains ordered elements, but different from Seq in that it cannot be empty. An Every is either a One, which contains one and only one element, or a Many, which contains two or more elements.

Here is a another variant for validating zip code with return type as Every.

def validateZipCodeWithOrEvery(zipCode:String): String Or One[ValidationError] = {
 if (zipCode.length == 6 ) Good(zipCode) else
   Bad(One(ValidationError("Zip code should be exactly 6 digits")))
}

Now let us Validate all addresses at Once. The below snippet uses Or Every for collecting all the errors while validating address. As you can see the return type it is: Address Or Every[ValidationError]

The Every[Validation] error will contain each error when validation fails of any address component e.g. street address or Zip Code.

def validateAddress(streetAddress:String, zipCode:String): Address Or Every[ValidationError] = {
   val stAddress = validateStAddressWithOrEvery(streetAddress)
   val zpCode = validateZipCodeWithOrEvery(zipCode)
 withGood(stAddress, zpCode){Address(_, _)}
}

The entire result will be wrapped in Good if all validations succeed while parsing address.

scala> validateAddress("Hello Street","123456")}
res0: Good(Address(Hello Street,123456))

The Error will be returned in the following way if Any validation fails:

scala> validateAddress("","ghb")}
res0: Bad(Many(ValidationError(Empty Adress!!), ValidationError(Zip code should be exactly 6 digits)))
scala> validateAddress("Non Empty","ghb")}
res1: Bad(One(ValidationError(Zip code should be exactly 6 digits)))<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>

You can visit the library if you want to explore more about scalactic and other ways in which the errors can be collected.
Hope you liked reading the material. You are more than welcome for any suggestions!!

knoldus-advt-sticker

About Manish Mishra

Manish is a Scala Developer at Knoldus Software LLP. He loves to learn and share about Functional Programming, Scala, Akka, Spark.
This entry was posted in Scala, scalatest. Bookmark the permalink.

4 Responses to Error handling in Scala: What, where and how?

  1. Pingback: Error Handling In Scala – Curated SQL

  2. Daniel Malpica says:

    Good(post)!

    Another good library for multiple validation (and parallel if you want) is the Cats library with the Validated monad which add also other functional monads: https://typelevel.org/cats/datatypes/validated.html

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s