2011-05-24 22 views
2

修补否则协类的典型例子如下:逆变哪里?

 
abstract class Stack[+A] { 
    def push[B >: A](x: B) : Stack[B] 
    def top: A 
    def pop: Stack[A] 

现在,如果我去掉隐含协方差和手动标注类,我得到这个:

 
abstract class Stack[A] { 
    def push[B >: A](x: B) : Stack[B] 
    def top [B >: A]: B 
    def pop [B >: A]: Stack[B] 
    def cast[B >: A]: Stack[B] 
} 

(快速正确性证明:a Stack[A]具有A类型的元素,因此如果B更宽容,我们总是可以返回A而不是B。同样,如果有任何堆栈A,我们可以使用它代替堆栈如果B可以接受A.)

但是现在我有点困惑:在这里应该有一个逆变,但是这里的所有子类型关系似乎都是一样的。发生了什么?

为了详细说明,我们定义了一个逆变函数F,例如(a -> b) -> (F b -> F a)。特别地,a -> r上的函数F a是逆变的,因为(a -> b) -> ((b -> r) -> (a -> r))只是通过组合函数。从形式主义的角度来看,我预计箭头会翻转。所以从纯粹的语法角度来看,当没有箭头翻转时(但应该是!),我会感到困惑吗?我写注释的Scala写作方式只是函数反转的“自然”表示,因此您甚至不会注意它?我的抽象类错了吗?第二个演示文稿有误导吗?

+0

而问题是......什么? “这里应该有什么反差”是什么意思? – 2011-05-24 16:17:42

+0

当我被告知一个关系是逆变的时,我期望存在一个子类型关系'A <:B'和一个相应的子类型关系'F A>:F B':我期望箭头翻转。但我在这里的任何地方都看不到。那么我写下的代码的特定部分是否使得'push'逆变的论点? – 2011-05-24 16:20:29

+0

在scala中,方差注释“ - ”表示类型是逆变的。你的堆栈类型没有这种关系,并且在任何情况下,即使你添加了一个'Stack [Apple]',也不是超级类型的'Stack [Fruit]'。 – 2011-05-24 16:36:51

回答

2

您正在寻找同样的关系。我们来考虑一下Stack[+A]是什么意思:如果CA的子类,则Stack[C]被视为Stack[A]的子类,即它可以在任何地方填写类A;所有方法都使用超类泛型进行了注释,这当然是正确的,正如您已经指出的那样。

但是,您还没有设计出原始类,以使push的参数处于逆变位置。当你对可以处理的东西施加限制时,这些关系自然会出现 - 那么,如果子类意味着该方法可以处理的次数更少,C[Subclass]可以作为C[Original]的超类,因为C[Original]可以处理子类可以处理的所有内容(以及更多)。但push可以处理任何东西您定义它的方式。因此,这正是类型边界和方差相互作用的原因:如果允许类型在正好处于逆变位置的点(即否则会限制你)的类型中加宽,那么允许你是协变的。否则,你必须是不变的或逆变的。 (流行不允许你是逆变的,所以你必须是不变的。例如,请参阅可变集合,其中不变是正是这个原因的规范 - 你不能自由地扩大类型推。)

+0

所以你说我应该写:push [A>:C](X:C):Stack [A]? – 2011-05-24 16:41:34

+1

@Edward Z. Yang - 我在说,如果没有'B>:A',你不能为'协变'堆栈'写'push',并且如果你自然而然地写出它,你就不会看到箭头翻转。你需要一个案例,箭头之间有一个_contrast_翻转和那些没有翻转的箭头。 – 2011-05-24 17:40:06