[英]Existentials vs Covariance in Scala
考虑下面的两个代码。 他们实现了同样的目标:只有这样的A[T]
-s可以存储在T
扩展C
的Container
中
但是,他们使用两种不同的方法来实现这一目标:
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)
}
编辑(回应Alexey的回答):
评论说:“我对第二种解决方案的问题在于,它不应该成为描述我可以存储在容器中的东西的责任,而不是什么,这应该是容器的责任。”
如果我有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
现在你不能用y: A[C1]
调用foo(y)
y: A[C1]
即使它有意义: yt
确实有类型C
如果在您的代码中发生这种情况,它可以修复,但第三方呢?
当然,这也适用于标准库类型:如果像Maybe
和List
这样的类型不是协变的,那么所有采用/返回它们的方法的签名都必须更复杂,或者许多程序当前都是有效的并且具有完美意义会打破。
它不应该是描述我可以存储在Container中的内容的责任,而不应该是Container的责任。
差异不是关于你可以存放在容器中的东西; 它是关于A[B]
是A[C]
的子类型。 这个论点有点像说你根本不应该extends
:否则class Apple extends Fruit
允许你将Apple
存储在Container[Fruit]
,并决定Container
责任。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.