Say I have a series of independent computations that need to be combined at the end:
def makeSauce(): Try[Sauce] = Success(Sauce("marinara"))
def makeCrust(): Try[Crust] = Failure(new RuntimeException("stale bread"))
def makeCheese(): Try[Cheese] = Failure(new IllegalArgumentException("moldy cheese"))
for {
sauce <- makeSauce()
crust <- makeCrust()
cheese <- makeCheese()
} yield {
Pizza(sauce, crust, cheese)
}
But if any of my computations fail, I want a comprehensive error message describing all of the failures, not just the first. The above code doesn't do what I want; it only shows me the first failure ("stale bread"
). Instead, it should throw a super-error message combining both of the others, which could be a sequence of exception objects, sequence of string error messages, or even just one giant concatenated string.
Alternatively, instead of throwing the exception, it could be composed into a top-level Failure
object:
(for {
sauce <- makeSauce()
crust <- makeCrust()
cheese <- makeCheese()
} yield (sauce, crust, cheese)) match {
case Success((sauce, crust, cheese)) => Pizza(sauce, crust, cheese)
case Failure(e) => throw e
}
The for-comprehension and the Try
here are just for illustration; a working solution doesn't have to use for
or Try
. However, obviously the above syntax is expressive and clean, and almost does what I want, so ideally the solution will stick to it as closely as possible.
How can I achieve something like this in Scala?
CodePudding user response:
scala.util.Try
behaves in a fail-fast manner. In a for-comprehension context it executed sequentially and halts at the first failure.
What you need is a structure that aggregates all failures like cats.data.Validated
or scalaz.Validation
.
CodePudding user response:
Validated is what you are looking for. It is in Cats for example.
import cats.data.Validated
import cats.implicits.catsSyntaxTuple3Semigroupal
def valid(input: String) = if (input.toIntOption.isEmpty) Validated.Invalid(s"$input is not a number")
else Validated.Valid(input.toInt)
def sumThreeNumbers(one:String, two:String, three: String) = (valid(one), valid(two), valid(three)).mapN((oneInt, twoInt, threeInt)=> oneInt twoInt threeInt)
sumThreeNumbers("1","2","3")
sumThreeNumbers("1","two","xxx")
//results
val res0: cats.data.Validated[String,Int] = Valid(6)
val res1: cats.data.Validated[String,Int] = Invalid(two is not a number.xxx is not a number.)