受Scala中协变和逆变的实际例子启发,我想提出一个更好的问题:
在设计库时,当确定类型参数应该是协变还是逆变时,您应该问自己一组特定的问题吗?还是应该将所有内容都设置为不变,然后根据需要进行更改?
受Scala中协变和逆变的实际例子启发,我想提出一个更好的问题:
在设计库时,当确定类型参数应该是协变还是逆变时,您应该问自己一组特定的问题吗?还是应该将所有内容都设置为不变,然后根据需要进行更改?
简单来说,它有意义吗?想一下Liskov替换原则。
如果 A <: B
,那么将 C[A]
传递给期望的 C[B]
是否有意义?如果是这样,请将其改为 C[+T]
。经典示例是不可变的 List
,假设 A
是 B
的子类型,则可以将 List[A]
传递给任何期望 List[B]
的方法。
两个反例:
可变序列是不变的,因为否则可能存在类型安全性问题(事实上,Java的协变 Array
存在此类问题,这就是为什么它在Scala中是不变的原因)。
不变的 Set
是不变的,即使它的方法与不可变的 Seq
非常相似。差异在于 contains
,它在集合上进行了类型标记,并且在序列上未标记(即接受 Any
)。因此,尽管否则可能使它成为协变,但对特定方法的增加类型安全性的渴望导致选择不变而不是协变。
如果 A <: B
,那么将期望的 C[A]
传递给 C[B]
是否有意义?如果是这样,请将其改为 C[-T]
。经典的例子是 Ordering
。虽然一些不相关的技术问题阻止了 Ordering
成为逆变,但直观上认为可以对超类 A
进行排序的任何内容也可以对 A
进行排序。因此,Ordering[B]
可以对所有类型为 B
的元素进行排序,即 A
的超类型,可以传递给希望获得 Ordering[A]
的东西。
虽然Scala的 Ordering
不是逆变的,但Scalaz的Order与预期相反。 Scalaz的另一个示例是其 Equal 特性。
Function1
(以及2、3等)。它在接收的参数中是反变的,在返回的参数中是协变的。请注意,Function1
用于许多闭包,并且闭包在许多地方使用,这些地方通常是Java使用(或将使用)单个抽象方法类的地方。