Reputation: 2058
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}
Upvotes: 8
Views: 4217
Reputation: 61766
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.
Upvotes: 2
Reputation: 40508
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)
}
Upvotes: 5
Reputation: 3464
Going through them one by one:
items.map(_.right.get)
You already know that these are all Rights. This will be absolutely fine.
items.map(_.right.getOrElse(null))
The .getOrElse
is unnecessary here as you already know it should never happen. I would recommend throwing an exception if you find a Left (somehow) though, something like this: items.map(x => x.right.getOrElse(throw new Exception(s"Unexpected Left: [$x]"))
(or whatever exception you think is suitable), rather than meddling with null
values.
items.map(_.asInstanceOf[Right[String, T]].value)
This is unnecessarily complicated. I also can't get this to compile, but I may be doing something wrong. Either way there's no need to use asInstanceOf
here.
items.flatMap(_.toOption)
I also can't get this to compile. items.flatMap(_.right.toOption)
compiles for me, but at that point it'll always be a Some and you'll still have to .get
it.
items.collect{case Right(item) => item}
This is another case of "it works but why be so complicated?". It also isn't exhaustive in the case of a Left item being there, but this should never happen so there's no need to use .collect
.
Another way you could get the right values out is with pattern matching:
items.map {
case Right(value) => value
case other => throw new Exception(s"Unexpected Left: $other")
}
But again, this is probably unnecessary as you already know that all values will be Right.
If you are going to partition results
like this, I recommend the first option, items.map(_.right.get)
. Any other options either have unreachable code (code you'd never be able to hit through Unit tests or real-life operation) or are unnecessarily complicated for the sake of "looking functional".
Upvotes: 0