4

Back2Basics: Exception Handling – #4

 3 years ago
source link: https://blog.knoldus.com/scala-exception-handling4/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Back2Basics: Exception Handling – #4

Reading Time: 3 minutes

In the previous series of blogs, we have seen how we can do error handling in Scala. In this blog, we will explore yet another way of handling exceptions using the functional style of programming.

Scalactic is a library which provides an “Either with attitude” named Or, designed for functional error handling. Or gives you more convenient chaining of map and flatMap calls (and for expressions) than Either.

Or is a class in the Scalactic library which represents a value with two possible types, one type being “good” and the other one being ”bad

One can wonder why to use Or when Either is such a wonderful feature provided with ease of writing and understanding as we wrap error in Left() and correct execution flow in Right(). But the thing with Either is that it treats both its Left and Right alternatives identically. It is by convention that Right should contain the valid result and Left should contain the error.

It was before Scala 2.12 was released, Or had more powers than Either as we could not perform monadic operations on Either directly, first Either has to be projected using left or right and then can be transformed using map or flatMap. But after the release of Scala 2.12, monadic operations could be performed directly on Either just like Or.

Let us define a method that parses name and another method that tells if the voter is eligible to vote or not:

xxxxxxxxxx
def parseName(input: String): Or[String, String] = {
val trimmed = input.trim
if (!trimmed.isEmpty) Good(trimmed) else Bad(s""""${input}" is not a valid name""")
}
case class Voter(name: String, age: Int)
case class VoterEligibility(name: String, eligible: Boolean)
def eligibleToVote(voter: Voter): Boolean Or String = {
voter.age match {
case age if age  Bad(s"$age < 0 Age can not be negative .")
case age if age  Bad(s"$age  Good(true)
  }
}

Let us now see if the voter can vote or not using method canVote

xxxxxxxxxx
def canVote(voter: Voter) = {
for {
name <- parseName(voter.name)
eligible <- eligibleToVote(voter)
 } yield VoterEligibility(name, eligible)
}

We can also use Either instead of Or, as mentioned above, they both have same powers so one can use Either or Or as preferred. Let us now call canVote method:

xxxxxxxxxx
 canVote(Voter("Bob", 22))

which will result in:

xxxxxxxxxx
res0: org.scalactic.Or[VoterEligibility,String] = Good(VoterEligibility(Bob,true))

we can easily perform map operation on it.

xxxxxxxxxx
canVote(Voter("Bob", 22)).map(voter => voter.name)
res0: org.scalactic.Or[String,String] = Good(Bob)

If we provide bad value,

xxxxxxxxxx
canVote(Voter(" ", 22))

Result will be

xxxxxxxxxx
res0: org.scalactic.Or[VoterEligibility,String] = Bad(" " is not a valid name)

In the above method, canVote, method calls are chained after one another, if parseName method returns a Bad value, eligibleToVote method will not be called, so, what do we do to report all the errors that are there?

Let’s say, we call canVote method with the following values:

xxxxxxxxxx
canVote(Voter(" ", -22))
The result will be:
res0: org.scalactic.Or[VoterEligibility,String] = Bad(" " is not a valid name)

Here only one error is displayed, but what do we do if we want all the errors that are there in the input values. Scalactic provides features to accumulate errors.

We can accumulate errors with Or. It is one of the differences between Either and Or. Or enables you to accumulate errors if the Bad type is an Every. An Every is either a One, which contains one and only one element, or a Many, which contains two or more elements.

To accumulate the errors, we need to rewrite the methods, as the return type of Bad type have to be changed to Every. Let’s rewrite the methods:

xxxxxxxxxx
def parseName(input: String): String Or One[String] = {
val trimmed = input.trim
if (!trimmed.isEmpty) Good(trimmed) else Bad(One((s""""${input}" is not a valid name""")))
}
def eligibleToVote(voter: Voter): Boolean Or One[String] = {
voter.age match {
 case age if age  Bad(One(s"$age < 0 Age can not be negative ."))
 case age if age  Bad(One(s"$age  Good(true)
  }
}

Now we can accumulate error using withGood method

xxxxxxxxxx
def canVote(voter: Voter) = {
val name: Or[String, One[String]] = parseName(voter.name)
val eligible: Or[Boolean, One[String]] = eligibleToVote(voter)
Accumulation.withGood(name, eligible) {VoterEligibility}
}

withGood method on Accumulation, applies the good values to the given function, here the name and age which you would provide will be applied in {…} and it will return the result wrapped in a Good otherwise it returns a Bad containing every error ie. a Bad whose Every includes every value that appears in any Bad.

So, calling,

xxxxxxxxxx
canVote(Voter(" ", -22))

will return :

xxxxxxxxxx
res0: org.scalactic.Or[VoterEligibility,org.scalactic.Every[String]] = Bad(Many(" " is not a valid name, -22 < 0 Age can not be negative .))

This is how we can accumulate errors using Or.

Feel free to suggest or comment.

Reference:

  1. http://doc.scalactic.org/3.0.1/#org.scalactic.Or
  1. http://longcao.org/2015/07/09/functional-error-accumulation-in-scala

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK