Переключение между BothT и 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)

person blouerat    schedule 30.04.2015    source источник
comment
Вовсе не дубликат, но см. мой аналогичный вопрос здесь.   -  person Travis Brown    schedule 30.04.2015
comment
Спасибо @TravisBrown, ответ довольно элегантный, мне он нравится. Может быть, нам нужно что-то вроде disjunctionedT или что-то в этом роде, чтобы оно работало с трансформерами. Я попробую.   -  person blouerat    schedule 30.04.2015