在 EitherT 和 Validation 之间切换以累积误差或遍历

2024-02-25

假设我有以下功能:

def getRemoteThingy(id: Id): EitherT[Future, NonEmptyList[Error], Thingy]

Given a List[Id],我可以轻松地检索List[Thingy]通过使用Traverse[List]:

val thingies: EitherT[Future, NonEmptyList[Error], List[Thingy]] = 
  ids.traverseU(getRemoteThingy)

它将使用Applicative实例为EitherT这将基于flatMap所以我只会得到第一个NonEmptyList[Error],它不会附加所有这些。那是对的吗?

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

def thingies2: EitherT[Future, NonEmptyList[Error], List[Thingy]] = 
  EitherT(ids.traverseU(id => getRemoteThingy(id).validation).map(_.sequenceU.disjunction))

它有效,我最后得到了所有错误,但这非常麻烦。我可以做到simpler通过使用Applicative作品:

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)
  )

比其他的更长,但所有的管道都可以轻松地在代码库中共享。

您认为我的解决方案怎么样?有没有更优雅的方法来解决这个问题?你通常如何应对?

非常感谢。

EDIT:

我有一种疯狂的解决方案,使用自然变换来拉皮条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)

None

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 EitherT 和 Validation 之间切换以累积误差或遍历 的相关文章

随机推荐