Home > Enterprise >  Is there a Scala pattern for collecting error messages from a sequence of independent computations?
Is there a Scala pattern for collecting error messages from a sequence of independent computations?

Time:09-17

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.)

 
  • Related