scalafor-comprehensioneither

Combining a sequence of Either function results using a for comprehension


Using a for comprehension, I can succinctly make a series of interdependent calls to one or more functions that return an Either, short circuiting when any returns a Left.

I can use this approach to call a single function with different arguments:

def getValue(value: Int) = value match {
  case 9 => Left("Invalid")
  case _ => Right(value)
}

// Right((1,2,8))
val values1 = for {
  a <- getValue(1)
  b <- getValue(2)
  c <- getValue(8)
} yield (a, b, c)

// Left(Invalid)
val values2 = for {
  a <- getValue(1)
  b <- getValue(2)
  c <- getValue(9)
} yield (a, b, c)

Let's say I have a sequence of values, e.g. Seq(1, 2, 9). Is there a way to write a similar for comprehension to achieve the same result?

For example, the following achieves the same result, though it doesn't short circuit, and it doesn't use a for comprehension:

val inputs = Seq(1, 2, 9)

val values3 = inputs.map(getValue).partitionMap(identity) match {
  case (Nil, v)        => Right(v)
  case (first :: _, _) => Left(first)
}

Solution

  • You could use foldLeftto traverse the inputs, and then use for comprehension inside foldLeft function.

    Since type of acc is Either , when we use for comprehension on it, it will make a short circuit.

    val result: Either[String, Seq[Int]] =
      inputs.foldLeft(Right(Seq.empty): Either[String, Seq[Int]]) { (acc, n) =>
        for {
          xs <- acc           
          x  <- getValue(n)   
        } yield xs :+ x      
      }
    

    You can add statement like _ = println(x) to debug it.

    scala-cli
    Welcome to Scala 3.7.2 (21.0.2, Java OpenJDK 64-Bit Server VM).
    Type in expressions for evaluation. Or try :help.
    
    scala> def getValue(value: Int) = value match {
         |   case 9 => Left("Invalid")
         |   case _ => Right(value)
         | }
    def getValue(value: Int): Either[String, Int]
    
    scala> val inputs1 = List(1,2,9,4,1)
    val inputs1: List[Int] = List(1, 2, 9, 4, 1)
    
    scala> val inputs2 = List(1,2,8)
    val inputs2: List[Int] = List(1, 2, 8)
    
    scala> def result(inputs: Seq[Int]) =
         |   inputs.foldLeft(Right(Seq.empty): Either[String, Seq[Int]]) { (acc, n) =>
         |     for {
         |       xs <- acc
         |       x  <- getValue(n)
         |       _   = println(x)
         |     } yield xs :+ x
         |   }
         |
    def result(inputs: Seq[Int]): Either[String, Seq[Int]]
    
    scala> result(inputs1)
    1
    2
    val res0: Either[String, Seq[Int]] = Left(Invalid)
    
    scala> result(inputs2)
    1
    2
    8
    val res1: Either[String, Seq[Int]] = Right(List(1, 2, 8))