“EitherT”不应该是协变的吗?尤其是左派?

Shouldn’t `EitherT` be covariant? Especially in the Left?

我有以下代码

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)
}

其中 SpecificErrorGenericError 的子类。出于某种原因,它不会编译抱怨 SpecificError 不是 GenericError。对吗?

我的意思是,Either[A, B] 应该是不可变的,那为什么不让它协变呢?我错过了什么吗?

好的,我找到了解决方法。

我仍然不知道为什么 EitherT 不是协变的,但你必须记住 Either 本身 协变的。 所以诀窍是告诉编译器为 EitherT 创建使用上限:

EitherT[Future, GenericError, UUID](futureOfEither)

左边有多个错误也有效(因为编译器被迫找到 LUB)但是 GenericError 必须扩展 ProductSerializable如果是trait,SpecificError是caseclass(参考this question as to why

...
}.map {
  case 1 => kitten.uuid.asRight
  case 2 => GenericError.SpecificError2.asLeft
  case _ => GenericError.SpecificError.asLeft
}
EitherT(futureOfEither)

针对 XorTOptionT 提出了相同的问题 here。回复是:

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 the S type constructor to be covariant. At the time I often wanted to wrap a Coyoneda in Free. The most recent versions of Cats and Scalaz have the Coyoneda essentially built into the Free, so this particular use might not be as desired now, but the general principle applies. The problem is that Coyoneda 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.

作为另一个非常巧妙的解决方法,您可以在 flatMap[E, T] 类型参数中指定抽象类型,例如:

// 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 }

FlatMap的情况下。对于 Map,您只能在右侧转换值和类型。