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
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
It should work:
def unfoldRes[A](x: Seq[Either[String, A]]) = x partition {_.isLeft} match {
case (Seq(), 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.
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))
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 Right
s and Left
s. 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))
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.
Here is the scalaz code:
_.sequence