不应该'EitherT`是协变的吗?特别是在左派?

时间:2017-06-16 12:55:59

标签: scala functional-programming scala-cats

我有以下代码

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]应该是不可变的,为什么不让它变得协变?我错过了什么吗?

3 个答案:

答案 0 :(得分:2)

XorTOptionT同样的问题被提出here。答复是:

  

在Scala中,方差既有正面也有负面(也许这就是他们如何决定方差表示法!:P)。这些优点/缺点已经在不同的场地多次讨论过,所以我现在不会讨论它们,但在我看来,在一天结束时你们必须“满足于他们自己”。

     

我认为这“对每个人都有”的观点意味着你不能强迫类型构造函数的方差。一个具体的例子是7.0系列中的scalaz.Free。它强制S类型构造函数是协变的。当时我经常想在Coyoneda中包裹Free。 Cats和Scalaz的最新版本Coyoneda基本上内置于Free,因此这种特殊用途可能不是现在所希望的,但一般原则适用。问题是Coyoneda是不变的,所以你根本无法做到这一点(没有一堆@uncheckedVariance)!通过使类型构造函数参数保持不变,最终可能会迫使人们更明确地了解类型,但我认为它胜过替代方案,您可以阻止它们完全使用您的类型。

答案 1 :(得分:0)

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

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

EitherT[Future, GenericError, UUID](futureOfEither)

左边有多个错误(因为编译器被迫找到LUB)但是 GenericError必须扩展ProductSerializable如果它是一个特征而SpecificError是一个案例类(参考this question as to why

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

答案 2 :(得分:0)

作为另一个非常整洁的解决方法,您可以在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,您只能在右侧变换值和类型。