cats
cats copied to clipboard
Proposal: lifting between monad transformers, simple effect types and raw types
In practice, I often encounter situations when I need to lift F[_] to EitherT or OptionT. For EitherT, the only current option is EitherT.right[E](foo: F[A]): EitherT[F, E, A]. Compare that to A-F[A] interop or OptionT-EitherT interop:
(a: A).pure[F]: F[A]
(o: OptionT[F, A]).toRight(error: E): EitherT[F, E, A]
(e: EitherT[F, E, A]).toOption: OptionT[F, A]
Similarly, I think it is a good idea to have rich wrappers such as to allow:
(fa: F[A]).toRight[E]: EitherT[F, E, A]
(fa: F[A]).toLeft[B]: EitherT[F, A, B]
(fe: F[Either[E, A]]).lift: EitherT[F, E, A]
(fo: F[Option[A]]).lift: OptionT[F, A]
The EitherT.right(foo: F[A]) is a bit cumbersome because:
- There's a redundancy of information: in general,
rightuniquely impliesEitherTwhen used onF[A] - You could solve the above redundancy by importing
EitherT.{right, ...}– however, there is a bunch of conversion methods defined on EitherT, and the need to manage the imports might introduce some mental overhead. Ideally, I'd like to be able to just import Cats once (e.g. currently I am usingimport cats._, cats.implicits._, cats.data._, cats.effect._) and forget about it. - There are extra parentheses:
right[E](fa)vsfa.toRight. Parentheses clutter code. - The syntax is not uniform with that of
A: if I can doa.pure[EitherT[...]], I'd also like to be able to dofa.toRight[E].
I agree EitherT.right(foo: F[A]) is a bit of typing, on the other hand, I think there is a reason we might want to stick with it.
(fa: F[A]).toRight[E]: EitherT[F, E, A] will be ambiguous to read with the existing a.asRight[E]: Either[A, E], so if we want to add this method the name has to be something like asEitherTRight to avoid the ambiguity. And if you do that, fa.asEitherTRight is exactly the same num of characters as EitherT.right(fa). Adding an extension method to any F[A] is a cost, and in this particular case, I am not sure if the benefit outweighs it.
On a side note, I am on the camp of abstracting any effect type F[_] as much as possible. There shouldn't be much code directly dealing with concrete effect types like EitherT and OptionT. Instead, most of the class/methods are declared as class MyClass[F[_]: MonadError[E, ?]]. If you take this approach, then mostly likely you just need to define a FunctionK from one concrete F[_] to another F[_] once per application. Then, among other benefits, there is less need for this kind of conversion methods.
why not use asRightT to be similar to asRight for Either syntax
liftEitherT and liftOptionT were added to typelevel/mouse in this PR. F[A].toRight[L]: EitherT[F, L, A] and F[A].toLeft[R]: EitherT[F, A, R] were considered but ultimately pulled from the PR since concerns were raised that it wasn't common enough of a use case to justify increasing compile time/ide responsiveness.