Should `flatten` work for a `Collection` of `Either`, `Try`, etc the way it works with `Option`?

Currently, I am doing this for some code that is a Seq[Either[L, R]], where converter is a function of String => Checkout where Checkout is a custom case class.

Using(Source.fromResource(str))(resource => resource.getLines()
      .map(converter)
      .filter(_.isRight)
      .map(_.getOrElse(Checkout("", "", LocalDate.of(1971, 1, 1))))
      .toSeq)

Another alternative would be:

 Using(Source.fromResource(str))(resource => resource.getLines()
      .map(converter)
      .collect{case Right(x) => x}
      .toSeq)

Both have a smell. The first particularly since I already filtered all the Right and I don’t like that I have to create a dummy object with a getOrElse to get the Right objects and just have a list or other collections just with Right elements. As for the collect, it also seems a bit off, but better than the first.

A List[Option] has a great feature of flatten

val xs = List(Some(3), None, Some(4), None)
xs.flatten

which results of course in

List(3, 4)

I would love to make my call above to be…

Using(Source.fromResource(str))(resource => resource.getLines()
      .map(converter)
      .flatten
      .toSeq)

where flatten for any collection would only keep the “positive” elements of a monadic type (Success, Right, etc) much in the same way it works with Option. Currently, flatten does not work with Either.

Seq[Option[A]]#flatten makes sense because None translates cleanly into Seq.empty and Some(_) translates cleanly into Seq(_). No information is lost, and the behavior is unambiguous.

This isn’t really the case with Either, as it’s use-case is specifically for those times when you want to keep around both possibilities.

As to alternatives, if you don’t mind (or already have) a dependency on scalaz or cats, you can use their traverse helper to switch the List[Either[_, _] into something like Either[_, List[_]] and handle the possible Left once at the end.

You could also use cats’ Either.toOption, or create an equivalent helper, and extract with .flatMap(_.toOption) if you need to capture successes despite the existence of failures.

Because there are a couple of different reasonable semantics you could want from List[Either[_,_]]#flatten, it’d be a really interesting discussion on what exactly the semantics of a built-in version should be.

5 Likes

If you are sure your function will match all elements, you can replace collect by map. e.g.

**Welcome to Scala 2.13.0 (OpenJDK 64-Bit Server VM, Java 1.8.0_222).
Type in expressions for evaluation. Or try :help.

(1 to 10).map(Right(_)).map { case Right(i) => i }
res1: IndexedSeq[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)**

Either has a toOption method that converts all Lefts to None and all Rights to Some, which you then could flatten over.

The problem is a classic map/reduce. If it is a common pattern in your domain, creating a library of functors will add semantics to the code.*

As a lisper, I think of Either as a pair (car/cdr). Really an equivalent of scala’s Tuple2. I’m not sure why it was added to the language. I might not be looking at the problem correctly from a Scala viewpoint.

  • One of the ways to think of monads is as categories of the edges of a category graph. I don’t know scala well enough. It’s frustrating to me not to have the representation is the language. I’ve thought that structured "type"s might be a help. Or package level functions and types. Traits are probably good enough. Scala isn’t trying to be Coq for category theory.

WRT the equivalence of Either and Tuple2: there’s an important distinction: a Tuple2 can only exist if both the right and left are present, while Either can only exist if exactly one of the left or right are present, so it’s a disjunction rather than a pair.

WRT more semantic types, I’d normally use cats.Validated for accumulating semantics and scala.Either for fail-fast semantics, but that’s kind of out of scope for this question, and I really didn’t want to be That Guy™ and drop a response suggesting they switch to an entirely different data type :slight_smile:

2 Likes

Either is also monadically right-biased. Really, it’s misnamed – idiomatically, it is usually used for monadic validation, where Left means error and Right means success. It’s very, very different from a straightforward pair…

3 Likes

Either is not an equivalent of Tuple2, it’s a dual, or opposite. Either[A, B] means A OR B; Tuple2[A, B] means A AND B. So their relationship is that of AND to OR.

1 Like

Thanks. I don’t always get bits in scala.