Scala是世界上最好的语言(一):Type Bound

栏目: Scala · 发布时间: 6年前

内容简介:这个语法应该是Scala最常用的语法之一。它比C#的类型约束走的更远,因为它既可以表示上界,也可以表示下界。不过在此之前需要搞清几个概念。我们知道Java允许数组协变,这会导致将一个子类添加到父类数组时的异常:从Scala一切皆方法的角度,就是数组的

这个语法应该是Scala最常用的语法之一。它比C#的类型约束走的更远,因为它既可以表示上界,也可以表示下界。不过在此之前需要搞清几个概念。

Variance Position

我们知道 Java 允许数组协变,这会导致将一个子类添加到父类数组时的异常:

String[] a1 = { "abc" };
Object[] a2 = a1;
a2[0] = new Integer(17);
String s = a1[0];
复制代码

从Scala一切皆方法的角度,就是数组的 update 方法的参数是一个类型参数。更一般的来看,这里 update 的参数是一个抗变点(contravariant position),或者说负向点(negative position)。其实我觉得负向点的说法更好,因为它可以与型变的概念区分开。一个抗变点不允许接受协变的类型参数。另外,还有2种型变点:协变点(covariant position),不变点(novariant position)。

由于类型参数本身也是一个类型,因此泛型是可以嵌套的。这给最终验证型变注解带来了一定的复杂性。在语言规范里是这样说明的:

The top-level of the type or template is always in covariant position. The variance position changes at the following constructs.

  • The variance position of a method parameter is the opposite of the variance position of the enclosing parameter clause.
  • The variance position of a type parameter is the opposite of the variance position of the enclosing type parameter clause.
  • The variance position of the lower bound of a type declaration or type parameter is the opposite of the variance position of the type declaration or parameter.

这里型变发生了翻转(flipped)。一个函数参数的型变点会翻转,同时类型参数(如果有下界或者型变注解)的型变点也会翻转。这是 Programming in Scala 中构造的示例:

abstract class Cat[-T, +U] {
def meow[W−](volume: T−, listener: Cat[U+, T−]−)
: Cat[Cat[U+, T−]−, U+]+ }
复制代码

注意到这里 Cat[U, T] 的U和T互换了位置,因为他们所在的型变点发生了翻转。比如,对于返回值而言,因为最外层的Cat的第一个类型参数是抗变的,所以U翻转为协变点,T为抗变点(规则2)。同样listener也发生了翻转(规则1)。

原理上, 方法的参数是逆变点,而返回值是协变点 。这主要是基于里氏替换原则的推理。举个例子:

abstract class Box[+F <: Fruit] {
  def fruit: F
  def contains(aFruit: Fruit) = fruit.name.equals(aFruit.name)
}
class AppleBox(apple: Apple) extends Box[Apple] {
  def fruit = apple
}
var fruitBox: Box[Fruit] = new AppleBox(new Apple)
var fruit: Fruit = fruitBox.fruit
复制代码

这里的F是典型的协变应用。不过,假设这里F是抗变的,那么就会出现问题,因为作为父类的AppleBox需要返回一个apple,而子类fruitBox并不能替换它。

最后,Scala默认的行为是不变(novariant)。这也是一个更符合逻辑的抉择。

Lower Bound

下界既可以是一个具体的类,也可以是一个类型参数。还是用 Programming in Scala 的例子,它通过下界允许对一个协变类调用包含逆变点的方法:

class Queue[+T] (private val leading: List[T], private val trailing: List[T] ) {
    ...
    def append[U >: T](x: U) = new Queue[U](leading, x :: trailing)
}
复制代码

这里可以将一个 Orange 追加到 Queue[Apple] 上得到一个 Queue[Fruit] 。注意到当作为 Queue[Fruit] 时,U必须是 Fruit 的超类,因此这个下界防止了前面Java代码中的赋值错误。

更一般的来说,这里的下界定义了一次翻转(规则3)。这其实很好理解:我们总是在父类中调用方法 append ,而T是U的子类。

Programming in Scala 在这里提到了术语声明处型变(declaration-site variance)与使用处型变(use-site variance)。这里的site是声明型变的位置。Scala是声明处型变的,这一点和C#相同。Java是使用处型变的,这种风格要求 程序员 必须很清楚类的可变性,缺少了编译器的支持。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Data Structures and Algorithms with JavaScript

Data Structures and Algorithms with JavaScript

Michael McMillan / O'Reilly Media / 2014-2-22 / USD 28.51

If you’re using JavaScript on the server-side, you need to implement classic data structures that conventional object-oriented programs (such as C# and Java) provide. This practical book shows you how......一起来看看 《Data Structures and Algorithms with JavaScript》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

随机密码生成器
随机密码生成器

多种字符组合密码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码