I have an error type hierarchy for peeling bananas:
sealed trait PeelBananaError
object PeelBananaError {
case object TooRipe extends PeelBananaError
case object NotRipeEnough extends PeelBananaError
}
And I have some results in EitherT
that we know can only fail in one of the two ways:
val peelBrownBananaResult: EitherT[Future, TooRipe, String] = ...
val peelGreenBananaResult: EitherT[Future, NotRipeEnough, String] = ...
Now I need to gather the String
results from the right and combine them for a final result:
val combinedResult: EitherT[Future, PeelBananaError, String] = for {
first <- peelBrownBananaResult
second <- peelGreenBananaResult
} yield (first + second)
But trying this out gives me a compile error:
cmd15.sc:2: inferred type arguments [PeelBananaError.NotRipeEnough.type,String] do not conform to method flatMap's type parameter bounds [AA >: PeelBananaError.TooRipe.type,D]
first <- peelBrownBananaResult
^
cmd15.sc:2: type mismatch;
found : String => cats.data.EitherT[scala.concurrent.Future,PeelBananaError.NotRipeEnough.type,String]
required: String => cats.data.EitherT[scala.concurrent.Future,AA,D]
first <- peelBrownBananaResult
^
Compilation Failed
It seems the compiler is unable to infer that the Left
types share a common inheritance in PeelBananaError
so the for comprension does not compile. Is there any workaround here? Some hint I can give to the compiler so I can run this for comprehension?
I have tried the following, but using .asInstanceOf
seems so hacky and the resulting code looks very ugly, is this really the only solution?
val combinedResult: EitherT[Future, PeelBananaError, String] = for {
first <- peelBrownBananaResult.asInstanceOf[EitherT[Future,PeelBananaError,String]]
second <- peelGreenBananaResult.asInstanceOf[EitherT[Future,PeelBananaError,String]]
} yield (first + second)
EitherT
monad transformer is not covariant in A
or B
type parameters
case class EitherT[F[_], A, B]
unlike Either
which is covariant in A
and B
sealed abstract class Either[+A, +B]
which is why Either
type-checks to a supertype
Either.left(TooRipe): Either[PeelBananaError, String] // ok
but EitherT
does not
EitherT.left(Future(TooRipe)): EitherT[Future, PeelBananaError, String] // error
However we can safely widen monad transformers
for {
first <- peelBrownBananaResult.leftWiden[PeelBananaError]
second <- peelGreenBananaResult.leftWiden[PeelBananaError]
} yield (first + second)
// : EitherT[Future, PeelBananaError, String] = ...
unlike unsafe widening with asInstanceOf
type T = Either[TooRipe.type, String]
val a = Either.left[PeelBananaError, String](new PeelBananaError {})
val b: Either[TooRipe.type, String] = a.asInstanceOf[T] // run-time error
val c: Either[TooRipe.type, String] = a.widen[T] // compile-time error
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With