我有以下代码
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)
}
SpecificError
是GenericError
的子类。由于某种原因,它没有编译抱怨SpecificError
不是GenericError
。是不是?
我的意思是,Either[A, B]
应该是不可变的,为什么不让它变得协变?我错过了什么吗?
答案 0 :(得分:2)
XorT
和OptionT
同样的问题被提出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
必须扩展Product
和Serializable
如果它是一个特征而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
,您只能在右侧变换值和类型。