Reputation: 7707
I have the following code
def addKitten(kitten: Kitten): EitherT[Future, GenericError, UUID] = {
val futureOfEither = db.run { // returns a Future[Int] with the number of rows written
kittens += kitten
}.map {
case 1 => kitten.uuid.asRight
case _ => GenericError.SpecificError.asLeft
}
EitherT(futureOfEither)
}
Where SpecificError
is a subclass of GenericError
. For some reason it does not compile complaining that a SpecificError
is not a GenericError
. Is it right?
I mean, Either[A, B]
should be immutable, so why not making it covariant? Am I missing something?
Upvotes: 3
Views: 1228
Reputation: 387
As another quite neat workaround you could specify abstract types in flatMap[E, T]
type parameters, for example:
// Given ADT
trait Err
case class E1() extends Err
case class E2() extends Err
// We could do (pseudo-code)
EitherT(E1()).flatMap[Err, Int] { x => 100 }
It is in case of FlatMap
. For Map
you could only transform value and type on the right side.
Upvotes: 0
Reputation: 170805
The same issue for XorT
and OptionT
was raised here. The reply was:
In Scala, variance has both positives and negatives (perhaps that's how they decided on variance notation! :P). These pros/cons have been discussed numerous times in various venues, so I won't go into them now, but in my opinion at the end of the day you kind of have to settle on "to each their own".
I think this "to each their own" perspective implies that you can't force variance on a type constructor. A concrete example is
scalaz.Free
in the 7.0 series. It forces theS
type constructor to be covariant. At the time I often wanted to wrap aCoyoneda
inFree
. The most recent versions of Cats and Scalaz have theCoyoneda
essentially built into theFree
, so this particular use might not be as desired now, but the general principle applies. The problem is thatCoyoneda
is invariant, so you simply couldn't do this (without a mess of@uncheckedVariance
)! By making type constructor parameters invariant, you may end up forcing people to be more explicit about types, but I think that it beats the alternative, where you can prevent them from being able to use your type at all.
Upvotes: 2
Reputation: 7707
Ok, I found a workaround.
I still have no idea why the EitherT
is not covariant but you have to remember that the Either
itself is covariant.
So the trick is to tell the compiler to use an upper bound for the EitherT
creation:
EitherT[Future, GenericError, UUID](futureOfEither)
Having more than one error as left works too (because the compiler is forced to find the LUB) but GenericError
has to extend Product
and Serializable
if it’s a trait and SpecificError
is a case class (refer to this question as to why)
...
}.map {
case 1 => kitten.uuid.asRight
case 2 => GenericError.SpecificError2.asLeft
case _ => GenericError.SpecificError.asLeft
}
EitherT(futureOfEither)
Upvotes: 1