Switch between EitherT and Validation to accumulate error or pass

Let's say I have the following function:

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

Given List[Id] , I can easily get List[Thingy] with Traverse[List] :

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

It will use the Applicative instance for EitherT , which will be based on flatMap , so I will only get the first NonEmptyList[Error] , it will not add all of them. It is right?

Now, if I really want to copy the errors, I can switch between EitherT and Validation . For instance:

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

It works, I get all errors at the end, but it is rather cumbersome. I can make this easier with the 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) ) 

Longer than others, but all plumbing can be easily split based on code.

What do you think of my decisions? Is there a more elegant way to solve this problem? How do you usually solve this?

Thank you very much.

EDIT:

I have a crazy solution using natural transformations for a Traversable pimp. You apparently need type aliases to work, so I redefined 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) 
+6
source share

Source: https://habr.com/ru/post/986302/


All Articles