Molochdaa
Molochdaa

Reputation: 2218

With a Scala Either, how do you stop at the first error, but gets the already computed values

For example, let say I have a function

def foo(): Either[String, Int] = ???

I want to call this function 3 times. If all the values are Right, I want the sum. If I have one Left, I want to get the error and the sum of all previous Right values (~ stop the computation at this point).

the only way I found to do this is :

List(foo, foo, foo).foldLeft((None, 0)) { 
  case ((Some(err), sum), _) =>  (Some(err), sum)
  case ((None, sum), fn) => fn() match {
    case Left(err) => (Some(err), sum)
    case Right(x) => (None, sum + x)
  }
}

Is there some generic functional programming feature (with cats or scalaz for example) to do that?

Upvotes: 3

Views: 436

Answers (3)

Oleg Pyzhcov
Oleg Pyzhcov

Reputation: 7353

Using Stream.span:

val (ints, rest) = Stream.continually(foo()).take(3).span(_.isRight)

val sum = ints.map(_.right.get).sum
val error = rest.headOption

Upvotes: 3

vvg
vvg

Reputation: 6385

Pretty much the same solution with tail recursive function:

 def sumRights(allFoos: List[() => Either[String, Int]], accum: Int = 0): (Option[String], Int) = {
    allFoos match {
      case Nil => (None, accum)
      case head :: tail =>
        head() match {
          case Right(x) => sumRights(tail, accum + x)
          case Left(err) => (Some(err), accum)
        }
    }

Another imperative solution is to split on a first Left with splitWhere, and sum up all values of first output list.

Upvotes: 0

Aivean
Aivean

Reputation: 10882

I'm not sure if scalaz of cats have something specifically for this, but here is how you can solve this in vanilla scala:

  1. lazy Stream:

    Stream.continually(foo()).take(3) match {
      case s =>
        val err = s.collectFirst {
          case Left(err) => err
        }
        val sum = s.takeWhile(_.isRight)
          .flatMap(_.right.toOption)
          .sum
    
        (err, sum)
    }
    

Stream will be calculated lazily and cached.

  1. Tail recursion:

    def rec(sum: Int = 0, i: Int = 2): (Option[String], Int) =
      if (i < 0) (None, sum) else foo() match {
        case Left(e) => (Some(e), sum)
        case Right(s) => rec(s + sum, i - 1)
      }
    

Upvotes: 0

Related Questions