3

Does Cats provide flattening akin to

implicit class FlattenListOfEither[L, R](l: List[Either[L, R]]) {
  def flattenM: List[R] = l collect { case Right(v) => v }
}

such that

val l1: List[Either[String, Int]] = List(Right(1), Right(2), Left("error"), Right(4))
l1.flattenM

outputs

List(1, 2, 4)

similar to how vanilla Scala flattens list of options

val l2: List[Option[Int]] = List(Some(1), Some(2), None, Some(4))
l2.flatten

which outputs

List(1, 2, 4)

separate gives the following syntax

import cats.implicits._
val (_, rights) = l1.separate
rights

which outputs

List(1, 2, 4)

however does there exist out-of-the-box flatten-like extension method which returns just the rights instead of the tuple?

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
  • 3
    I doubt such method exists. It make no sense on general abstractions like **Monads**, as you would need a way to see any nested element as of the same type of the outer. The case with option works, because on the _stdlib_ an **Option** can be _implicitly_ converted into an **IterabeOnce** _(of just one element)_ - being honest I do not like that, but that is another discussion. Now, if it would be an especial method only for inner **Eithers** then `separate` is the one that makes more sense - IMHO, the best would be yo create your own extension method, using `collect` – Luis Miguel Mejía Suárez Aug 01 '19 at 13:02

3 Answers3

7

I think the easiest is to use mapFilter provided by the FunctorFilter typeclass. It looks like this:

def mapFilter[A, B](fa: F[A])(f: (A) ⇒ Option[B]): F[B]

Where F[A] could be List[A], or Vector[A] or any other type that allows filtering.

If we apply this function to your list, we just need to turn the Either[A, B]s into Option[B]s. This is as easy as calling toOption. With that the full solution looks like this:

import cats.implicits._

val l1: List[Either[String, Int]] = List(Right(1), Right(2), Left("error"), Right(4))

l1.mapFilter(_.toOption)
// List(1, 2, 4)
Luka Jacobowitz
  • 22,795
  • 5
  • 39
  • 57
3

I just wanted to remark that

listOfEithers.flatMap(_.toOption)

with plain vanilla Scala-collection methods seems to do what you want:

val x = List(Right(1), Right(2), Left("error"), Right(4))
x.flatMap(_.toOption) // res0: List[Int] = List(1, 2, 4)
Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
  • 1
    If using vanilla, personally I find `collect { case Right(v) => v }` clearer, but I feel `flatten` communicates the concept perfectly (if it existed) – Mario Galic Aug 01 '19 at 21:53
  • 1
    @MarioGalic I don't know. I find LukaJacobowitz's `mapFilter` slightly shorter and nicer than `collect`, but then I find that `flatMap` looks almost like `mapFilter`, but does not require that I look it up in the documentation, but then again I see that `collect` expresses the intent cleaner than `flatMap`. It's circular, like rock-paper-scissors, not sure which one I like more :D – Andrey Tyukin Aug 01 '19 at 22:00
1

What about partitionMap in Scala 2.13? (note sure what you mean by shorter):

   val l1: List[Either[String, Int]] = List(Right(1), Right(2), Left("error"), Right(4))
    val (_, rights) = l1.partitionMap(identity)
    println(rights)
 // Displays
 // List(1, 2, 4)

Valy Dia
  • 2,781
  • 2
  • 12
  • 32