Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I convert a list of Either to a list of Right values?

Tags:

scala

either

I do some data conversion on a list of string and I get a list of Either where Left represents an error and Right represents a successfully converted item.

val results: Seq[Either[String, T]] = ... 

I partition my results with:

val (errors, items) = results.partition(_.isLeft)

After doing some error processing I want to return a Seq[T] of valid items. That means, returning the value of all Right elements. Because of the partitioning I already knew that all elements of items Right. I have come up with five possibilities of how to do it. But what is the best in readability and performance? Is there an idiomatic way of how to do it in Scala?

// which variant is most scala like and still understandable?
items.map(_.right.get)
items.map(_.right.getOrElse(null))
items.map(_.asInstanceOf[Right[String, T]].value)
items.flatMap(_.toOption)
items.collect{case Right(item) => item}
like image 853
Christian Rodemeyer Avatar asked Oct 16 '22 11:10

Christian Rodemeyer


2 Answers

Using .get is considered "code smell": it will work in this case, but makes the reader of the code pause and spend a few extra "cycles" in order to "prove" that it is ok. It is better to avoid using things like .get on Either and Option or .apply on a Map or a IndexedSeq.

.getOrElse is ok ... but null is not something you often see in scala code. Again, makes the reader stop and think "why is this here? what will happen if it ends up returning null?" etc. Better to avoid as well.

.asInstanceOf is ... just bad. It breaks type safety, and is just ... not scala.

That leaves .flatMap(_.toOption) or .collect. Both are fine. I would personally prefer the latter as it is a bit more explicit (and does not make the reader stop to remember which way Either is biased).

You could also use foldRight to do both partition and extract in one "go":

 val (errors, items) = results.foldRight[(List[String], List[T])](Nil,Nil) { 
    case (Left(error), (e, i)) => (error :: e, i)
    case ((Right(result), (e, i)) => (e, result :: i)
 }
like image 82
Dima Avatar answered Oct 31 '22 16:10

Dima


Starting in Scala 2.13, you'll probably prefer partitionMap to partition.

It partitions elements based on a function which returns either Right or Left. Which in your case, is simply the identity:

val (lefts, rights) = List(Right(1), Left("2"), Left("3")).partitionMap(identity)
// val lefts:  List[String] = List(2, 3)
// val rights: List[Int]    = List(1)

which let you use lefts and rights independently and with the right types.

like image 33
Xavier Guihot Avatar answered Oct 31 '22 16:10

Xavier Guihot