How to reduce Seq[Either[A,B]] to Either[A,Seq[B]]?

ScalaFunctional ProgrammingScalazScala Cats

Scala Problem Overview


Given a sequence of eithers Seq[Either[String,A]] with Left being an error message. I want to obtain an Either[String,Seq[A]] where I get a Right (which will be a Seq[A]), if all elements of the sequence are Right. If there is at least one Left (an error message), I'd like to obtain the first error message or a concatenation of all error messages.

Of course you can post cats or scalaz code but I'm also interested in code not using it.

Edit

I've changed the title, which originally asked for an Either[Seq[A],Seq[B]] to reflect the body of the message.

Scala Solutions


Solution 1 - Scala

Edit: I missed that the title of your question asked for Either[Seq[A],Seq[B]], but I did read "I'd like to obtain the first error message or a concatenation of all error messages", and this would give you the former:

def sequence[A, B](s: Seq[Either[A, B]]): Either[A, Seq[B]] =
  s.foldRight(Right(Nil): Either[A, List[B]]) {
    (e, acc) => for (xs <- acc.right; x <- e.right) yield x :: xs
  }

scala> sequence(List(Right(1), Right(2), Right(3)))
res2: Either[Nothing,Seq[Int]] = Right(List(1, 2, 3))

scala> sequence(List(Right(1), Left("error"), Right(3)))
res3: Either[java.lang.String,Seq[Int]] = Left(error)

Using Scalaz:

val xs: List[Either[String, Int]] = List(Right(1), Right(2), Right(3))

scala> xs.sequenceU
res0:  scala.util.Either[String,List[Int]] = Right(List(1, 2, 3))

Solution 2 - Scala

Given a starting sequence xs, here's my take:

xs collectFirst { case x@Left(_) => x } getOrElse
  Right(xs collect {case Right(x) => x})

This being in answer to the body of the question, obtaining only the first error as an Either[String,Seq[A]]. It's obviously not a valid answer to the question in the title


To return all errors:

val lefts = xs collect {case Left(x) => x }
def rights = xs collect {case Right(x) => x}
if(lefts.isEmpty) Right(rights) else Left(lefts)

Note that rights is defined as a method, so it'll only be evaluated on demand, if necessary

Solution 3 - Scala

It should work:

def unfoldRes[A](x: Seq[Either[String, A]]) = x partition {_.isLeft} match {
  case (Nil, r) => Right(r map {_.right.get})
  case (l, _) => Left(l map {_.left.get} mkString "\n")
}

You split your result in left and right, if left is empty, build a Right, otherwise, build a left.

Solution 4 - Scala

Here is the scalaz code:

_.sequence

Solution 5 - Scala

Starting in Scala 2.13, most collections are provided with a partitionMap method which partitions elements based on a function which maps items to either Right or Left.

In our case, we don't even need a function that transforms our input into Right or Left to define the partitioning since we already have Rights and Lefts. Thus a simple use of identity!

Then it's just a matter of matching the resulting partitioned tuple of lefts and rights based on whether or not there are lefts:

eithers.partitionMap(identity) match {
  case (Nil, rights)       => Right(rights)
  case (firstLeft :: _, _) => Left(firstLeft)
}

// * val eithers: List[Either[String, Int]] = List(Right(1), Right(2), Right(3))
//         => Either[String,List[Int]] = Right(List(1, 2, 3))
// * val eithers: List[Either[String, Int]] = List(Right(1), Left("error1"), Right(3), Left("error2"))
//         => Either[String,List[Int]] = Left("error1")

Details of the intermediate step (partitionMap):

List(Right(1), Left("error1"), Right(3), Left("error2")).partitionMap(identity)
// => (List[String], List[Int]) = (List("error1", "error2"), List(1, 3))

Solution 6 - Scala

Building on Kevin's solution, and stealing a bit from Haskell's Either type, you can create a method partitionEithers like so:

def partitionEithers[A, B](es: Seq[Either[A, B]]): (Seq[A], Seq[B]) =
  es.foldRight (Seq.empty[A], Seq.empty[B]) { case (e, (as, bs)) =>
    e.fold (a => (a +: as, bs), b => (as, b +: bs))
  }

And use that to build your solution

def unroll[A, B](es: Seq[Either[A, B]]): Either[Seq[A], Seq[B]] = {
  val (as, bs) = partitionEithers(es)
  if (!as.isEmpty) Left(as) else Right(bs)
}

Solution 7 - Scala

I'm not used to use Either - here is my approach; maybe there are more elegant solutions:

def condense [A] (sesa: Seq [Either [String, A]]): Either [String, Seq [A]] = {
  val l = sesa.find (e => e.isLeft)
  if (l == None) Right (sesa.map (e => e.right.get)) 
  else Left (l.get.left.get)
}

condense (List (Right (3), Right (4), Left ("missing"), Right (2)))
// Either[String,Seq[Int]] = Left(missing)
condense (List (Right (3), Right (4), Right (1), Right (2)))
// Either[String,Seq[Int]] = Right(List(3, 4, 1, 2))

Left (l.get.left.get) looks a bit funny, but l itself is a Either [A, B], not an Either [A, Seq[B]], and needs rewrapping.

Solution 8 - Scala

My answer is similar to @Garrett Rowe's: But it uses foldLeft (Also see: https://stackoverflow.com/questions/4085118/why-foldright-and-reduceright-are-not-tail-recursive) and prepends to Seq rather than appending to Seq (See: https://stackoverflow.com/questions/1320139/why-is-appending-to-a-list-bad).

scala> :paste
// Entering paste mode (ctrl-D to finish)

def partitionEitherSeq[A,B](eitherSeq: Seq[Either[A,B]]): (Seq[A], Seq[B]) =
  eitherSeq.foldLeft(Seq.empty[A], Seq.empty[B]) { (acc, next) =>
  val (lefts, rights) = acc
  next.fold(error => (lefts :+ error, rights), result => (lefts, rights :+ result))
}

// Exiting paste mode, now interpreting.

partitionEitherSeq: [A, B](eitherSeq: Seq[Either[A,B]])(Seq[A], Seq[B])

scala> partitionEitherSeq(Seq(Right("Result1"), Left("Error1"), Right("Result2"), Right("Result3"), Left("Error2")))
res0: (Seq[java.lang.String], Seq[java.lang.String]) = (List(Error1, Error2),List(Result1, Result2, Result3))

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionziggystarView Question on Stackoverflow
Solution 1 - ScalaBen JamesView Answer on Stackoverflow
Solution 2 - ScalaKevin WrightView Answer on Stackoverflow
Solution 3 - ScalaNicolasView Answer on Stackoverflow
Solution 4 - ScalaTony MorrisView Answer on Stackoverflow
Solution 5 - ScalaXavier GuihotView Answer on Stackoverflow
Solution 6 - ScalaGarrett RoweView Answer on Stackoverflow
Solution 7 - Scalauser unknownView Answer on Stackoverflow
Solution 8 - ScalaSudheer AedamaView Answer on Stackoverflow