Reputation: 379
I am completely new to Scala. AFAIK, Either
encapsulate failure handling allowing chain operations without writing boilerplate code repeatedly. It allows also circuit break the continuation of execution. But this may not always what I want. e.g. for the following code, if both name
and age
are invalid, the makePerson
function will not return both errors.
Can you guys please suggest a way?
case class Person(name: Name, age: Age)
sealed class Name(val value: String)
sealed class Age(val value: Int)
case class Person(name: Name, age: Age){
}
sealed class Name(val value: String)
sealed class Age(val value: Int)
object Person{
def makeName(name: String): Either[String, Name] = {
if (name == "" || name == null) Left("Name is empty.") else Right(new Name(name))
}
def makeAge(age: Int): Either[String, Age] = {
if (age < 0) Left("Age is out of range.") else Right(new Age(age))
}
def makePerson(name: String, age: Int): Either[String, Person] = {
mkName(name).map2(mkAge(age))(Person(_, _))
}
}
def map2[EE >: E, B, C](b: Either[EE, B])(f: (A, B) => C): Either[EE, C] = {
val func = (aa: A) => b.map(bb => f(aa, bb))
this.flatMap(func)
}
def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] = this match {
case Left(e) => Left(e)
case Right(a) => f(a)
}
Upvotes: 5
Views: 827
Reputation: 48410
Here are examples of error accumulation using Validated
and Parallel
as suggested by @Luis:
Validated
import cats.data.ValidatedNec
import cats.implicits._
case class Person(name: Name, age: Age)
case class Name(value: String)
case class Age(value: Int)
object validatedPerson extends App {
private def validateName(name: String): ValidatedNec[String, Name] =
if (name == "" || name == null) "Name is empty.".invalidNec else Name(name).validNec
private def validateAge(age: Int): ValidatedNec[String, Age] =
if (age < 0) "Age is out of range.".invalidNec else Age(age).validNec
def validatePerson(name: String, age: Int): ValidatedNec[String, Person] = {
(validateName(name), validateAge(age)).mapN(Person)
}
println(validatePerson(name = "Joe", age = 21))
println(validatePerson(name = "", age = -42))
}
which outputs
Valid(Person(Name(Joe),Age(21)))
Invalid(Chain(Name is empty., Age is out of range.))
where we see in the Invalid
case all the errors were accumulated.
Parallel
import cats.data.{EitherNel, NonEmptyList}
import cats.instances.parallel._
import cats.syntax.parallel._
object parValidatedPerson extends App {
private def validateName(name: String): EitherNel[String, Name] =
if (name == "" || name == null) Left(NonEmptyList.one("Name is empty.")) else Right(Name(name))
private def validateAge(age: Int): EitherNel[String, Age] =
if (age < 0) Left(NonEmptyList.one("Age is out of range.")) else Right(Age(age))
def validatePerson(name: String, age: Int): Either[NonEmptyList[String], Person] = {
(validateName(name), validateAge(age)).parTupled.map(Person.tupled)
}
println(validatePerson(name = "Joe", age = 21))
println(validatePerson(name = "", age = -42))
}
which outputs
Right(Person(Name(Joe),Age(21)))
Left(NonEmptyList(Name is empty., Age is out of range.))
where we see in the Left
case all the errors were accumulated.
Upvotes: 8