Scala 在EitherT和验证之间切换以累积误差或遍历

Scala 在EitherT和验证之间切换以累积误差或遍历,scala,scalaz,monad-transformers,scalaz7,Scala,Scalaz,Monad Transformers,Scalaz7,假设我有以下功能: def getRemoteThingy(id: Id): EitherT[Future, NonEmptyList[Error], Thingy] 给定一个列表[Id],我可以使用遍历[List]轻松检索列表[Thingy]: val thingies: EitherT[Future, NonEmptyList[Error], List[Thingy]] = ids.traverseU(getRemoteThingy) 它将使用Applicative实例作为Eith

假设我有以下功能:

def getRemoteThingy(id: Id): EitherT[Future, NonEmptyList[Error], Thingy]
给定一个
列表[Id]
,我可以使用
遍历[List]
轻松检索
列表[Thingy]

val thingies: EitherT[Future, NonEmptyList[Error], List[Thingy]] = 
  ids.traverseU(getRemoteThingy)
它将使用
Applicative
实例作为
EitherT
,该实例将基于
flatMap
,因此我将只获取第一个
NonEmptyList[Error]
,它不会追加所有实例。对吗

现在,如果我真的想累积错误,我可以在
EitherT
Validation
之间切换。例如:

def thingies2: EitherT[Future, NonEmptyList[Error], List[Thingy]] = 
  EitherT(ids.traverseU(id => getRemoteThingy(id).validation).map(_.sequenceU.disjunction))
它是有效的,我在最后得到了所有的错误,但它相当麻烦。我可以通过使用
Applicative
composition使其更简单:

type ValidationNelError[A] = Validation[NonEmptyList[Error], A]
type FutureValidationNelError[A] = Future[ValidationNelError[A]]
implicit val App: Applicative[FutureValidationNelError] =
  Applicative[Future].compose[ValidationNelError]

def thingies3: EitherT[Future, NonEmptyList[Error], List[Thingy]] = 
  EitherT(
    ids.traverse[FutureValidationNelError, Thingy](id => 
      getRemoteThingy(id).validation
    ).map(_.disjunction)
  )
比其他管道更长,但所有管道都可以在代码库中轻松共享

你觉得我的解决方案怎么样?有没有更优雅的方法来解决这个问题?你通常是怎么处理的

多谢各位

编辑:

我有一个类似于疯子的解决方案,它使用自然转换来实现pimp
Traversable
。您显然需要类型别名才能工作,这就是为什么我重新定义了
getRemoteThingy

type FutureEitherNelError[A] = EitherT[Future, NonEmptyList[String], A]

def getRemoteThingy2(id: Id): FutureEitherNelError[Thingy] = getRemoteThingy(id)

implicit val EitherTToValidation = new NaturalTransformation[FutureEitherNelError, FutureValidationNelError] {
  def apply[A](eitherT: FutureEitherNelError[A]): FutureValidationNelError[A] = eitherT.validation
}

implicit val ValidationToEitherT = new NaturalTransformation[FutureValidationNelError, FutureEitherNelError] {
  def apply[A](validation: FutureValidationNelError[A]): FutureEitherNelError[A] = EitherT(validation.map(_.disjunction))
}

implicit class RichTraverse[F[_], A](fa: F[A]) {
  def traverseUsing[H[_]]: TraverseUsing[F, H, A] = TraverseUsing(fa)
}

case class TraverseUsing[F[_], H[_], A](fa: F[A]) {
  def apply[G[_], B](f: A => G[B])(implicit GtoH: G ~> H, HtoG: H ~> G, A: Applicative[H], T: Traverse[F]): G[F[B]] =
    HtoG(fa.traverse(a => GtoH(f(a))))
}

def thingies4: FutureEitherNelError[List[Thingy]] = 
  ids.traverseUsing[FutureValidationNelError](getRemoteThingy2)

一点也不重复,但请看我的类似问题。谢谢@TravisBrown。答案非常优雅,我喜欢。也许我们需要类似于析取DT的东西,或者让它与变压器一起工作的东西。我试试看。