2

After doing a match on a bunch of Eithers that have type Either[String, A] (where A is more than one type), I'd like to accumulate any strings on the left into a list.

(a, b, c, d, e) match {
  case (Right(a), Right(b), Right(c), Right(d), Right(e)) => {
    "All good, use a, b, c, d, and e!"
  }
  case anythingElse => {
    val strings = accLefts(anythingElse)
    doSomethingWithStrings(strings)
  }
}

If I try to .productIterator.toList the tuple, I end up with List[Any]. If I handle each failing case separately (combinations of Rights and Lefts), I end up with an exponential number of case statements.

How can I get a List[Either[String, Any]] at the end there to pass to my accLefts call? Or should I have done something other than a match?

Jim Hunziker
  • 14,111
  • 8
  • 58
  • 64

3 Answers3

5

This is precisely the kind of thing that ValidationNEL in Scalaz (which is essentially a beefed-up Either) is designed to support. For example, suppose we have the following setup using Scalaz 7:

import scalaz._, Scalaz._

case class Person(first: String, last: String, initial: Char, age: Int)

val first = "John".successNel[String]

val last = "Doe".successNel[String]
val badLast = "Empty last name".failureNel[String]

val initial = 'H'.successNel[String]
val badInitial = "Non-alphabetic MI".failureNel[Char]

val age = 45.successNel[String]
val badAge = "Negative age provided".failureNel[Int]

Note that the Nel here stands for non-empty list, and that "John".successNel[String] is more or less equivalent to Right("John"): Either[List[String], String], etc.

Now we can write the following:

scala> println((first |@| last |@| initial |@| age)(Person.apply))
Success(Person(John,Doe,H,45))

Or:

scala> println((first |@| badLast |@| initial |@| badAge)(Person.apply))
Failure(NonEmptyList(Empty last name, Negative age provided))

Or:

scala> println((first |@| badLast |@| badInitial |@| badAge)(Person.apply))
Failure(NonEmptyList(Empty last name, Non-alphabetic MI, Negative age provided))

Any errors are accumulated in the left side of the ValidationNEL. See e.g. my answer here for more details.

Community
  • 1
  • 1
Travis Brown
  • 138,631
  • 12
  • 375
  • 680
3

Perhaps with nested pattern matching?

case anythingElse => {
    val strings = anythingElse
                    .productIterator
                    .collect { case Left(str: String) => str }
                    .toList
    doSomethingWithStrings(strings)
}

Note that str: String here is to guide type inference so strings would have type List[String] not List[Any]

om-nom-nom
  • 62,329
  • 13
  • 183
  • 228
  • 1
    Small nitpick: `str: String` does more than just "guide type inference", it is actually fully part of the pattern matching (it might look like mere type ascription, but here it is not) and will perform a runtime cast. – Régis Jean-Gilles Mar 02 '13 at 10:14
0

I'd probably create a set of utility functions like

def fromTuple2[A, That](t: Tuple2[A,A])(implicit bf : CanBuildFrom[Nothing, A, That]): That =
  (bf.apply() += (t._1, t._2)).result();

for all the n-tuples you need. While it's a lot of boiler-plate code, it's just one-time job. And then you can do things like:

val e1: Either[String,Int] = Right(3);
val e2: Either[String,String] = Left("3");
val test: List[Either[String,Any]] = fromTuple2(e1, e2);

Perhaps better, we can use enrichment implicit methods such as

implicit def fromTuple2Impl[A](t: Tuple2[A,A]) = new {
  def asCollection[That](implicit bf : CanBuildFrom[Nothing, A, That]): That =
    (bf.apply() += (t._1, t._2)).result();
}

to write just

val test: List[Either[String,Any]] = (e1, e2).asCollection;

Edit: We can even enrich tuples to be Traversables, which gets us all methods like toList, folding etc.:

implicit def fromTuple2Impl3[A](t: Tuple2[A,A]) = new Traversable[A] {
  def asCollection[That](implicit bf : CanBuildFrom[Nothing, A, That]): That =
    (bf.apply() += (t._1, t._2)).result();
  override def foreach[U](f: (A) => U): Unit = {
    f(t._1); f(t._2);
  }
}

With some more work, we could take it further to implement IndexedSeq.

Petr
  • 62,528
  • 13
  • 153
  • 317