首页 文章

哪里是逆变?

提问于
浏览
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]
}

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

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

为了详细说明,我们定义了一个逆变函子 F ,这样 (a -> b) -> (F b -> F a) . 特别是, a -> r 上的仿函数 F a 是逆变的,只需通过编写函数即可实现 (a -> b) -> ((b -> r) -> (a -> r)) . 从形式主义的角度来看,我希望箭头能够翻转 . 所以从纯粹的句法角度来看,当没有箭头翻转时我会感到困惑(但应该有!)我的注释方式是将Scala简单地写成函数逆变的表示,这样你甚至都不会注意到它吗?我的抽象类错了吗?第二次演讲有什么误导吗?

1 回答

  • 2

    你're looking at the same relationship. Let'想想 Stack[+A] 的含义:如果 CA 的子类,那么 Stack[C] 被视为 Stack[A] 的子类,即它可以填写任何地方的类 A ;正如你所指出的那样,所有使用超集泛型注释的方法都是正确的 .

    但是你没有设计原始类来使 push 的参数处于逆变位置 . 当您对可以处理的内容施加限制时,这些关系自然会出现 - 那么,如果子类意味着该方法可以处理更少, C[Subclass] 充当 C[Original] 的超类,因为 C[Original] 可以处理子类可以处理的所有内容(以及更多) . 但 push 可以按照您定义的方式处理任何事情 .

    所以这正是类型边界和方差的相互作用:如果你允许类型扩展正好在那些处于逆变位置的点(即否则会限制你),那么你就可以是协变的 . 否则你必须是不变的或逆变的 . (Pop不允许你是逆变的,所以你必须是不变的 . 例如,参见可变集合,其中不变性是正常的这个原因 - 你不能自由扩大类型推 . )

相关问题