我试图通过使用下限创建新的不可变类型的方法来了解协方差
class ImmutableArray[+T](item: T, existing: List[T] = Nil) {
private val items = item :: existing
def append[S >: T](value: S) = new ImmutableArray[S](value, items)
}
我知道类型参数T
不能在append方法中使用,因为它违反了规则但如果我说Customer
类和子类Student
我仍然可以制作类型U
Student
。
我可以看到这有效,但为什么这不违反规则?我可以理解,如果我有一个Student
的列表,然后添加了Customer
我只能返回Customer
的列表,原因是不允许分配Customer
到Student
,因为它是父类型。但为什么我可以使用Student
?
我错过了什么?
谢谢Blair
答案 0 :(得分:13)
您的课程提供2项涉及T的操作:
构建
nextImmutableArray = new ImmutableArray(nextT, priorImmutableArray)
由于此操作,类型参数T必须是共变量:+ T。这允许您使用参数设置构造为类型的对象(T或T的子类型)。
想一想:通过加入 Valencia Orange构建一个Oranges数组是有效的。
组合
nextImmutableArray.append(newItemTorAncestor)
此方法不会附加到您的数据结构中。它需要两个独立的元素(您的数组实例 this 和一个额外的对象),它在新构造的数组中组合。您可以考虑将方法名称更改为 appendIntoCopy 。更好的是,您可以使用名称 + 。但为了最正确并与Scala惯例一致,最好的名称是:+ 。
当你问一个特定问题时,为什么我会对一个'随机'方法名称感到困惑?
因为该方法的确切性质决定了返回的数据结构是否是(a)与T(b)共变体的非变体与T(c)反变体与T.
当组合数组和元素时,新创建的数据结构必须具有一个类型参数,该参数是共同祖先类型的超类型。否则它不能包含原始元素。通常,当执行“a:+ b”时,其中A是数组[A],b是类型B,结果数据结构是数组[Some_SuperType_Of_Both_A_and_B]。
想想:如果我从一系列橘子开始,然后添加柠檬,我最终会得到一系列柑橘类水果(不是橙子,脐橙,也不是柠檬)。< /强>
方法规则(输入严格,适应输出):
在追加的情况下:从T开始,输出数据结构=对比变量为T,类型S使用T作为下限,因此输入参数=与S的共变量。这意味着如果T1是子类型然后,ImmutableArray [T1]是ImmutableArray [T2]的子类型,并且只要符合后者,它就可以替换,所有方法都遵循Liskov的替换原则。
答案 1 :(得分:10)
第一个问题:
我知道类型参数T不能在append方法中使用,因为它违反了规则
好吧,它可以使用。 S >: T
只是意味着如果您传入的S
类型等于T
或其参与者,则会使用S
。如果您将子级别的类型传递给T
,则会使用T
。
scala> class Animal
defined class Animal
scala> class Canine extends Animal
defined class Canine
scala> class Dog extends Canine
defined class Dog
scala> new ImmutableArray[Canine](new Canine)
res6: ImmutableArray[Canine] = ImmutableArray@a47775
scala> res6.append(new Animal)
res7: ImmutableArray[Animal] = ImmutableArray@1ba06f1
scala> res6.append(new Canine)
res8: ImmutableArray[Canine] = ImmutableArray@17e4626
scala> res6.append(new Dog)
res9: ImmutableArray[Canine] = ImmutableArray@a732f0
上面做res6.append(new Dog)
仍然给你ImminedArray类型的犬。如果你想某种方式它是完全合理的,因为添加Dog to Canine Array仍将保留阵列Canine。但是将动物添加到犬阵列会使它成为动物,因为它不再是完美的犬(可以是磨牙或其他东西)。
这是一个很好的例子,说明为什么通常会知道反变体类型声明使其适合写入(您的情况)和读取的协方差。
在您的示例中,我认为混淆可能是因为您正在将S >: T
与S super T
(来自Java世界)进行比较。使用S super T
,您必须具有超级类T
的参数类型,并且它不允许您将子类型的参数传递给T
。在scala中,编译器会处理这个问题(感谢类型推断)。
答案 2 :(得分:4)
考虑以下层次结构:
class Foo
class Bar extends Foo { def bar = () }
class Baz extends Bar { def baz = () }
和你的类似:
class Cov[+T](val item: T, val existing: List[T] = Nil) {
def append[S >: T](value: S) = new Cov[S](value, item :: existing)
}
然后我们可以为每个Foo
子类型构建三个实例:
val cFoo = new Cov(new Foo)
val cBar = new Cov(new Bar)
val cBaz = new Cov(new Baz)
需要bar
个元素的测试函数:
def test(c: Cov[Bar]) = c.item.bar
它拥有:
test(cFoo) // not possible (otherwise `bar` would produce a problem)
test(cBaz) // ok, since T covariant, Baz <: Bar --> Cov[Baz] <: Cov[Bar]; Baz has bar
现在append
方法,回到上限:
val cFoo2 = cBar.append(new Foo)
这没关系,因为Foo >: Bar
,List[Foo] >: List[Bar]
,Cov[Foo] >: Cov[Bar]
。
现在,您的bar
访问权限正确无误:
cFoo2.item.bar // bar is not a member of Foo
要理解为什么需要上限,想象以下是可能的
class Cov[+T](val item: T, val existing: List[T] = Nil) {
def append(value: T) = new Cov[T](value, item :: existing)
}
class BarCov extends Cov[Bar](new Bar) {
override def append(value: Bar) = {
value.bar // !
super.append(value)
}
}
然后你可以写
def test2[T](cov: Cov[T], elem: T): Cov[T] = cov.append(elem)
以下非法行为将被允许:
test2[Foo](new BarCov, new Foo) // BarCov <: Cov[Foo]
value.bar
将调用Foo
。使用(正确)上限,您将无法像假设的最后一个例子那样实现append
:
class BarCov extends Cov[Bar](new Bar) {
override def append[S >: Bar](value: S) = {
value.bar // error: value bar is not a member of type parameter S
super.append(value)
}
}
所以类型系统仍然健全。
答案 3 :(得分:2)
它的工作原理是因为append方法返回的是比原始类更广泛的类。 我们进行一些实验。
scala> case class myIntClass(a:Int)
defined class myIntClass
scala> case class myIntPlusClass(a:Int, b:Int)
defined class myIntPlusClass
scala> class ImmutableArray[+T](item: T, existing: List[T] = Nil){
|
| private val items = item :: existing
|
| def append[S >: T](value: S) = new ImmutableArray[S](value,items)
| def getItems = items
| }
defined class ImmutableArray
scala> val ia = new ImmutableArray[myIntClass](myIntClass(3))
ia: ImmutableArray[myIntClass] = ImmutableArray@5aa91edb
scala> ia.getItems
res15: List[myIntClass] = List(myIntClass(3))
scala> ia.append(myIntPlusClass(3,5))
res16: ImmutableArray[Product with Serializable] = ImmutableArray@4a35a157
scala> res16.getItems
res17: List[Product with Serializable] = List(myIntPlusClass(3,5), myIntClass(3))
scala> res16
res18: ImmutableArray[Product with Serializable] = ImmutableArray@4a35a157
所以你可以在这里添加一个派生类,但它只能起作用,因为结果数组的基类型被降级为最小公分母(在本例中为Serializable)。
如果我们尝试在结果数组上强制派生类型,它将无法工作:
scala> ia.append[myIntPlusClass](myIntPlusClass(3,5))
<console>:23: error: type arguments [myIntPlusClass] do not conform to method append's type parameter bounds [S >: myIntClass]
ia.append[myIntPlusClass](myIntPlusClass(3,5))
尝试做同样的make append返回一个派生类型数组将不起作用,因为T不是S的子类:
scala> class ImmutableArray[+T](item: T, existing: List[T] = Nil){
|
| private val items = item :: existing
|
| def append[S <: T](value: S) = new ImmutableArray[S](value,items)
| def getItems = items
| }
<console>:21: error: type mismatch;
found : List[T]
required: List[S]
def append[S <: T](value: S) = new ImmutableArray[S](value,items)