具有保留类型的序列的通用序列

Generic sequence of Either to Either of sequence with preserved type

我想要一个函数,可以将 Either[A, B] 的任何可迭代类型 C[_] 转换为 Either[C[A], C[B]]

我成功了,但我使用了 asInstanceOf 方法,我觉得这种方法在某些情况下可能会失败(我还不知道那会是什么场景,因为我不太明白 CanBuildFrom 解决).

我认为我应该使用自定义实现它 CanBuildFrom 但我希望有更简单的方法来实现它。

这是我的方法:

type IterableCollection[A[_], B] = A[B] with Iterable[B]

implicit class IterableEither[C[_], A, B](self: IterableCollection[C, Either[A, B]]) {
  def accumulate: Either[IterableCollection[C, A], IterableCollection[C, B]] = {
    val failures = self.collect { case x @ Left(_) => x.value }.asInstanceOf[IterableCollection[C, A]]

    if (failures.nonEmpty) Left(failures)
    else Right(self.collect { case x @ Right(_) => x.value }.asInstanceOf[IterableCollection[C, B]])
  }
}

我用 Scala 编程有一段时间了,但从未依赖过 asInstanceOf 因此我有点害怕将这种代码引入生产环境。你们看到没有演员的方法吗?

这是一个不依赖于 asInstanceOf 的合理通用提案:

import scala.language.higherKinds

object EitherAccumulatorExample {

  import scala.collection.{IterableLike, TraversableOnce}
  import scala.collection.generic.CanBuildFrom  
  implicit class EitherAccumulator[C[X] <: IterableLike[X, C[X]], A, B](wrapped: C[Either[A, B]]) {
    def accumulate[CA <: TraversableOnce[_], CB](
      implicit 
      cbfA: CanBuildFrom[C[Either[A, B]], A, CA],
      cbfB: CanBuildFrom[C[Either[A, B]], B, CB]
    ): Either[CA, CB] = {
      val failures: CA = wrapped.collect{ case x @ Left(_) => x.value }(cbfA)
      if (failures.nonEmpty) Left(failures)
      else {
        val successes: CB = wrapped.collect { case x @ Right(_) => x.value }(cbfB)
        Right(successes)
      }
    }
  }

  val example = List.empty[Either[Int, Char]]
  val foo: Either[List[Int], List[Char]] = example.accumulate

  val example2 = Vector.empty[Either[String, Double]]
  val bar: Either[Vector[String], Vector[Double]] = example2.accumulate

}

我个人比较建议看一下Validated, it should have a Traverse实例,它又提供了一个sequence方法,也许这就是你想要的。

我想我会用猫来尝试这个并想出了这个:

object SO {

  import cats.instances.either._
  import cats.instances.list._
  import cats.syntax.traverse._

  // type alias for the Iterable C
  type C[A] = Iterable[A]

  def doIt[A, B](c: C[Either[A, B]]): Either[A, C[B]] =
    c.toList.sequence[Either[A, ?], B].map(_.to[C])
}

请注意,我使用的是 Kind Projector,因此 '?'

对于猫来说,这将是一个遍历 either 通过 Validated 并返回到 Either 的函数。转换为Validated的原因是序列需要Applicative实例。

import cats.Traverse
import cats.data.{NonEmptyList, ValidatedNel}
import cats.implicits._

def accSequence[T[_], A, B](tab: T[Either[A, B]])(implicit T: Traverse[T]): Either[NonEmptyList[A], T[B]] =
  tab.traverse[ValidatedNel[A, ?], B](_.toValidatedNel).toEither

val result: Either[NonEmptyList[Int], List[String]] = accSequence(List(Left(1), Right("A"), Left(2)))