문제

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?

도움이 되었습니까?

해결책 2

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]

다른 팁

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.

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.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top