How to reduce Seq[Either[A,B]] to Either[A,Seq[B]]?
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.
Solution 1:
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:
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:
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:
Here is the scalaz code:
_.sequence