逆变在哪里?
修补协变类的典型示例如下:
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]
has A
类型的元素,因此如果 B
更宽松,我们始终可以返回 A
来代替 B
。类似地,给定任何 A
堆栈,如果 B 可以接受 A,我们可以使用它来代替 B
堆栈。)
但现在我有点困惑:有这里应该是逆变,但这里所有的子类型关系似乎都是相同的。发生了什么?
为了更详细地说明,我们定义一个逆变函子 F
使得 (a -> b) -> (F b -> F a)
。特别是,a -> 上的函子
是逆变的,如 F a
r(a -> b) -> ((b -> r) -> (a -> r))
只需组合函数即可。从形式主义的角度来看,我预计箭头会翻转。因此,从纯粹的语法角度来看,当没有箭头翻转时我会感到困惑(但应该有!)我编写 Scala 的注释方式是否只是函数逆变的“自然”表示,这样你甚至不会注意到它?我的抽象类错了吗?第二次演示是否存在误导性内容?
A canonical example of patching up an otherwise covariant class is as follows:
abstract class Stack[+A] { def push[B >: A]( x: B ) : Stack[B] def top: A def pop: Stack[A]
Now, if I remove the implicit covariance and manually annotate the class, I get this:
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] }
(Quick correctness proof: a Stack[A]
has elements of type A
, so if B
is more permissive we can always return an A
in place of B
. Similarly, given any stack of A
, we can use it in place of a stack of B
if B can accept A.)
But now I'm a little confused: there should be contravariance somewhere here, but all of the subtype relations here seem to be the same. What happened?
To elaborate anymore, we define a contravariant functor F
such that (a -> b) -> (F b -> F a)
. In particular, the functor F a
on a -> r
is contravariant, as (a -> b) -> ((b -> r) -> (a -> r))
simply by composing the functions. From a formalism perspective, I expect arrows to be flipping. So from a purely syntactic perspective, I get confused when no arrows are flipping (but there should be!) Is my annotated way of writing the Scala simply a "natural" representation of the contravariance of functions, such that you don’t even notice it? Is my abstract class wrong? Is there something misleading about the second presentation?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
你正在寻找同样的关系。让我们考虑一下
Stack[+A]
的含义:如果C
是A
的子类,那么Stack[C]被视为
Stack[A]
的子类,即它可以在任何地方填充类A
;对于所有用超集泛型注释的方法,这当然是正确的,正如您所指出的。但是您还没有设计原始类来使
push
的参数处于逆变位置。当您对可以处理的内容施加限制时,这些关系自然会出现 - 然后,如果子类意味着该方法可以处理更少的内容,则C[Subclass]
充当C[Original] 的超类]
因为C[Original]
可以处理子类可以处理的所有内容(甚至更多)。但是push
可以按照您定义的方式处理任何事情。因此,这正是类型边界和方差相互作用的方式:如果您允许在逆变位置的那些点上进行类型加宽(即否则会限制您),那么您就可以协变。否则你必须是不变的或逆变的。 (Pop 不允许你逆变,所以你必须保持不变。例如,参见可变集合,其中不变性正是出于这个原因的规范 - 你不能自由地扩展类型推。)
You're looking at the same relationship. Let's think about what
Stack[+A]
means: ifC
is a subclass ofA
, thenStack[C]
is treated as a subclass ofStack[A]
, i.e., it can fill in for classA
anywhere; with all methods annotated with superset generics, this is of course true, as you have pointed out.But you haven't designed your original class to make the argument of
push
be in the contravariant position. These relations naturally arise when you impose restrictions on what you can handle--then, if a subclass means that the method can handle less,C[Subclass]
acts as a superclass ofC[Original]
sinceC[Original]
can handle everything that the subclass can handle (and more). Butpush
can handle anything the way you've defined it.So this is exactly how type bounds and variance interact: if you allow type widening in exactly those spots that are in contravariant position (i.e. which would otherwise restrict you), then you are allowed to be covariant. Otherwise you must be invariant or contravariant. (Pop doesn't allow you to be contravariant, so you'd have to be invariant. See, for example, the mutable collections, where invariance is the norm for exactly this reason--you're not free to widen the type on push.)