Cats: Add a ValidatedT to give ApplicativeError to any Applicative.

Created on 11 Aug 2017  路  17Comments  路  Source: typelevel/cats

currently it only has MonadError when F[_]: Monad but I think you can have ApplicativeError when F[_]: Applicative.

Most helpful comment

Hey - some related ramblings - hopefully they'll be of some use.

We're currently using cats in the payment-api we're working on at The Guardian (still relatively new to it all, but improving slowly). We utilise EitherT[Future, A, B] a lot since most of our computations are asynchronous and sequential. However, at one point we want to make 2 concurrent, independent network requests and accumulate the errors.

As a bit of a noob, I was surprised to see an EitherT data type, but not a ValidatedT data type in the cats core library. After some reading (this thread and SO), I _think_ this is because monads (in general) do not compose (so that you have to write a specific monad transformer for e.g. Either) whereas, because applicatives do compose no such specific transformer is required for Validated.

Whilst this is neat (hope I've actually understood this correctly), it does require users of the library to understand these general concepts about composition, to be aware of the Nested data type, and to be comfortable creating their own wrapper for ValidatedT using Nested. They then have to convince the rest of the team that this approach is sound. If the ValidatedT data isn't going to be included, then I think it'd be nice to have a section in the Validated documentation explaining how Validated could be composed - it'd useful both as guidance and as a referencing when discussing with peers. I'd be happy to give it a try, but would likely fall short of the standard required 馃檲.

All 17 comments

I think this again is because the ap defined using Applicative[F] is inconsistent with the one using Monad[F]. That is the reason why there is no Apply and Applicative for EitherT, which implies that ApplicativeError would suffer from the same problem, right?
See https://github.com/typelevel/cats/issues/1467#issuecomment-305853570 and the linked issues.

I think for the same reason Haskell also only has
Monad m => Applicative (EitherT e m)

I didn't know it was inconsistent. It's not obvious to me why we can't make it consistent, but no huge deal. We can convert to ValidatedT.

actually, there is no ValidatedT.

There is no ValidatedT? How so? Applicatives compose. ValidatedT[F, E, ?] would just be a wrapper on top of Nested[F, Validated[E, ?], ?], I believe.

The point of ValidatedT, I think, is to get ApplicativeError from Applicative. You can't do that currently with Nested.

Maybe we want ApplicativeError on Nested[F, G, ?] if you have F[_]: Applicative and G[_]: ApplicativeError. Seems like you could also do the opposite perhaps: F[_]: ApplicativeError and G[_]: Applicative If true, it is not 100% clear which should be preferred.

I think an explicit ValidatedT offers some usability wins when the point is to add error handling to an Applicative.

I think there is no ValidatedT because that name invokes the idea of a monad transformer, which Validated cannot have. Of course as Edmund mentions you can have other effects composed with Validated through Nested.

Assuming the definition is general I could see such a Nested type class instance for ApplicativeError being useful. I wouldn't want to see a ValidatedT though since it would just be a special case of Nested and it would seem like we're privileging Validated. Maybe a type alias though if this is a common enough thing people want to do.

There are a lot of utility methods, similar to what we have in EitherT that you would want. Like mapInvalid, isValid, etc... just using Nested means we don't have those.

Applicative transformers seem to be just as useful a concept as monad transformers; I don't know if all applicatives actually admit ways to produce inductive instances for all applicative-focused MTL classes, and that is the chief use-case for monad transformers.

Additionally, I agree with @johnynek that unless we're going to provide a nicer syntax for Nested, applicative transformers do present a usability benefit.

Even more additionally, I'm sitting on the TwistedT applicative transformer until we have newtypes.

Is anyone working on this already?

https://github.com/typelevel/cats/pull/1837 might be able to help with some of the use cases described here :)

@leandrob13 are you still interested in working on this?

This seems to be the biggest chunk of work yet to be done in 1.0.0-RC1. If no one else is interested, I may give it a shot. @johnynek, is there any precedence of ValidateT or should I just try copy EitherT?

I'm still not a hundred percent convinced this is something we'd need in core for the reasons @adelbertc mentioned. Though, I do agree that there might be a usability benefit for some of the cases mentioned in here.
However, I think a very large portion of those use cases could be dealt with using Parallel with EitherT. So I think maybe adding an instance for ApplicativeError and Applicative and type alias could be enough?

So I went out to create an ApplicativeError[Nested[F, G, ?], E] given Applicative[F] and ApplicativeError[G, E], but then realized implementing handleErrorWith:

def handleErrorWith[A](fa: Nested[F, G, A])(f: E => Nested[F, G, A]): Nested[F, G, A]

is basically impossible AFAIK (unless we add Traverse to both F and G, or Monad for F).

In the same way, I don't think it's at all possible to define ApplicativeError[ValidatedT[F, E, ?], E] when F is only an Applicative (or atleast I wasn't able to solve it)
Analogous to the above example:

def handleErrorWith(fa: F[Validated[E, A])(f: E => F[Validated[E, A]]): F[Validated[E, A]]

To my knowledge this can only work if F is a Monad:

def handleErrorWith(fa: F[Validated[E, A])(f: E => F[Validated[E, A]]): F[Validated[E, A]] =
  F.flatMap(fa)({
    case Valid(a) => F.pure(Valid(a))
    case Invalid(e) => f(e)
  })

However, it's totally possible to define it "inside out", i.e. ApplicativeError[Nested[F, G, ?], E] given ApplicativeError[F, E] and Applicative[G] and it seems to be lawful as well. I added a PR, if that's something we want. :)

I think after #1893, this can be closed, unless we still think a ValidatedT would be useful, even though it can only form an ApplicativeError with a Monad.

Hey - some related ramblings - hopefully they'll be of some use.

We're currently using cats in the payment-api we're working on at The Guardian (still relatively new to it all, but improving slowly). We utilise EitherT[Future, A, B] a lot since most of our computations are asynchronous and sequential. However, at one point we want to make 2 concurrent, independent network requests and accumulate the errors.

As a bit of a noob, I was surprised to see an EitherT data type, but not a ValidatedT data type in the cats core library. After some reading (this thread and SO), I _think_ this is because monads (in general) do not compose (so that you have to write a specific monad transformer for e.g. Either) whereas, because applicatives do compose no such specific transformer is required for Validated.

Whilst this is neat (hope I've actually understood this correctly), it does require users of the library to understand these general concepts about composition, to be aware of the Nested data type, and to be comfortable creating their own wrapper for ValidatedT using Nested. They then have to convince the rest of the team that this approach is sound. If the ValidatedT data isn't going to be included, then I think it'd be nice to have a section in the Validated documentation explaining how Validated could be composed - it'd useful both as guidance and as a referencing when discussing with peers. I'd be happy to give it a try, but would likely fall short of the standard required 馃檲.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

alexandru picture alexandru  路  4Comments

chuwy picture chuwy  路  4Comments

diesalbla picture diesalbla  路  4Comments

Atry picture Atry  路  5Comments

tg44 picture tg44  路  4Comments