考虑下面的两个代码。他们实现了相同的目标:只有这样的 A[T]
-s 可以存储在Container
在哪里 T
扩展 C
然而,他们使用两种不同的方法来实现这一目标:
1) 存在主义
2) 协方差
我更喜欢第一个解决方案,因为那样 A
保持简单。我有什么理由想要使用第二种解决方案(协方差)?
我对第二种解决方案的问题是它是 不自然从某种意义上说,它不应该是 A
-s 描述我可以在 Container 中存储什么以及不能存储什么的责任,这应该是 Container 的责任。第二种解决方案也是更复杂一旦我想开始对 A
进行操作然后我必须处理所有与协方差相关的东西。
使用第二种(更复杂、更不自然)解决方案我会得到什么好处?
object Existentials extends App {
class A[T](var t:T)
class C
class C1 extends C
class C2 extends C
class Z
class Container[T]{
var t:T = _
}
val c=new Container[A[_<:C]]()
c.t=new A(new C)
// c.t=new Z // not compile
val r: A[_ <: C] = c.t
println(r)
}
object Cov extends App{
class A[+T](val t:T)
class C
class C1 extends C
class C2 extends C
class Z
class Container[T]{
var t:T = _
}
val c: Container[A[C]] =new Container[A[C]]()
c.t=new A(new C)
//c.t=new A(new Z) // not compile
val r: A[C] = c.t
println(r)
}
编辑(回应阿列克谢的回答):
评论:
“我对第二种解决方案的问题是,从某种意义上说,描述我可以在 Container 中存储什么而不是什么,这应该是 Container 的责任,这是不自然的。”
如果我有
class A[T](var t:T)
这意味着我只能存储 A[T]
-s 而不是( A[S]
其中 S<:T
)在容器中,在任何容器中。但是,如果我有
class A[+T](var t:T)
然后我可以存储A[S]
在哪里 S<:T
以及在任何容器中。所以当声明
A
是不变的还是协变的,我决定什么类型的 A[S] 可以存储在容器中(如上所示),这个决定发生在 A
的声明中.但是,我认为,这个决定应该发生在容器的声明中,因为它是特定于容器的,允许进入该容器的只有
A[T]
。 -s 或 A[S]
在哪里 S<:T
-s。换句话说,改变
A[T]
中的方差具有全局效果,同时从 A[T]
更改容器的类型参数至A[_<:S]
对容器本身有明确的局部影响。所以这里的“变化应该有局部影响”的原则也有利于存在主义的解决方案。
最佳答案
在第一种情况下 A
更简单,但在第二种情况下,它的客户是。由于您使用 A
的地方通常不止一个。 ,这通常是一个值得权衡的选择。你自己的代码演示一下:当你需要写A[_ <: C]
在第一种情况下(在两个地方),您可以使用 A[C]
在第二个。
此外,在第一种情况下,您可以只写 A[C]
在哪里 A[_ <: C]
真的很需要。假设你有一个方法
def foo(x: A[C]): C = x.t
现在你不能调用
foo(y)
与 y: A[C1]
即使它是有道理的:y.t
确实有类型 C
.当您的代码中发生这种情况时,可以修复它,但是第三方呢?
当然,这也适用于标准库类型: if 类型如
Maybe
和 List
不是协变的,所有获取/返回它们的方法的签名都必须更复杂,或者许多当前有效且完全有意义的程序会中断。it should not be A-s responsibility to describe what I can store in a Container and what not, that should be the Container's responsibility.
差异与您可以在容器中存储什么无关。大约是什么时候
A[B]
是 A[C]
的子类型.这个论点有点像说你不应该有 extends
根本:否则class Apple extends Fruit
允许您存储 Apple
在 Container[Fruit]
, 并决定是 Container
的责任。
关于scala - Scala中的存在与协方差,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46021771/