Переключение между EitherT и Validation для накопления ошибки или хода
Скажем, у меня есть следующая функция:
def getRemoteThingy(id: Id): EitherT[Future, NonEmptyList[Error], Thingy]
Учитывая List[Id]
Я легко могу получить List[Thingy]
используя Traverse[List]
:
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
состав:
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)
)
Дольше, чем другие, но всю сантехнику можно легко распределить по всей базе кода.
Что вы думаете о моих решениях? Есть ли более элегантный способ решить эту проблему? Как вы обычно справляетесь с этим?
Большое спасибо.
РЕДАКТИРОВАТЬ:
У меня есть своего рода решение для сумасшедшего, использующего естественные преобразования для сутенера 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)