“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)
}
其中 SpecificError
是 GenericError
的子类。出于某种原因,它不会编译抱怨 SpecificError
不是 GenericError
。对吗?
我的意思是,Either[A, B]
应该是不可变的,那为什么不让它协变呢?我错过了什么吗?
好的,我找到了解决方法。
我仍然不知道为什么 EitherT
不是协变的,但你必须记住 Either
本身 是 协变的。
所以诀窍是告诉编译器为 EitherT
创建使用上限:
EitherT[Future, GenericError, UUID](futureOfEither)
左边有多个错误也有效(因为编译器被迫找到 LUB)但是 GenericError
必须扩展 Product
和Serializable
如果是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)
针对 XorT
和 OptionT
提出了相同的问题 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
,您只能在右侧转换值和类型。
我有以下代码
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]
应该是不可变的,那为什么不让它协变呢?我错过了什么吗?
好的,我找到了解决方法。
我仍然不知道为什么 EitherT
不是协变的,但你必须记住 Either
本身 是 协变的。
所以诀窍是告诉编译器为 EitherT
创建使用上限:
EitherT[Future, GenericError, UUID](futureOfEither)
左边有多个错误也有效(因为编译器被迫找到 LUB)但是 GenericError
必须扩展 Product
和Serializable
如果是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)
针对 XorT
和 OptionT
提出了相同的问题 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 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.
作为另一个非常巧妙的解决方法,您可以在 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
,您只能在右侧转换值和类型。