Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Scalaz have something to accumulate in both error and success?

I started to use Scalaz 7 Validation and/or disjunction to process a list of possibly failing operation and managing their result.

There is two well documented case for that kind of use cases:

1/ You want to check a list of conditions on something, and accumulate each error if any. Here, you always go the end of list, and in case of any error, you have failure as global result. And that's an applicative functor at work.

2/ You want to execute several steps that may fail, and stop on the first one failing. Here, we have a monad that goes nicely in Scala for-comprehension.

So, I have two other use cases that are among the same lines, but don't seems to feet well on any precedent case: I want to process a list of step, possibly failing, and accumulate both error and success results (ex: it's a list of modification on files, errors may happen because that's the outer world, and success are patch that I want to keep for later).

The difference on the two use case is only if I want to stop early (on the first error) or go to the end of the list.

OK, so what is the correct thing for that ?

(writting the question leads me to think that it's just a simple foldLeft, does it ? I will let the question here to validate, and if anybody else wonder)

like image 673
fanf42 Avatar asked Aug 10 '12 14:08

fanf42


3 Answers

Take a look at Validation#append or its alias Validation#+|+. Given two validations, if both are success, it returns success of the values appended. If both are failures, it returns failure of the values appended. Otherwise, it returns the successful value. This requires an implicit Semigroup instance for the success type.

like image 111
mpilquist Avatar answered Nov 01 '22 01:11

mpilquist


I'd do something like this:

scala> List(1.success[String], 2.success[String], "3".failure[Int], "4".failure[Int]).partition(_.isSuccess)
res2: (List[scalaz.Validation[java.lang.String,Int]], List[scalaz.Validation[java.lang.String,Int]]) = (List(Success(1), Success(2)),List(Failure(3), Failure(4)))

scala> val fun = (_:List[Validation[String, Int]]).reduceLeft(_ append _)
fun: List[scalaz.Validation[String,Int]] => scalaz.Validation[String,Int] = <function1>

scala> fun <-: res2 :-> fun
res3: (scalaz.Validation[String,Int], scalaz.Validation[String,Int]) = (Success(3),Failure(34))

UPD: With #129 and #130 merged, you can change fun to (_:List[Validation[String, Int]]).concatenate or (_:List[Validation[String, Int]]).suml

Or bimap like this:

scala> List(1.success[String], 2.success[String], "3".failure[Int], "4".failure[Int]).partition(_.isSuccess).bimap(_.suml, _.suml)
res6: (scalaz.Validation[java.lang.String,Int], scalaz.Validation[java.lang.String,Int]) = (Success(3),Failure(34))
like image 23
George Avatar answered Nov 01 '22 01:11

George


What you need is approximately switching an Either[E, A] into a Writer[List[E], A]. The Writer monad logs the errors you encountered.

like image 1
ron Avatar answered Nov 01 '22 00:11

ron