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

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

内容简介:这个语法应该是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是使用处型变的,这种风格要求 程序员 必须很清楚类的可变性,缺少了编译器的支持。


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

查看所有标签

猜你喜欢:

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

游戏编程算法与技巧

游戏编程算法与技巧

【美】Sanjay Madhav / 刘瀚阳 / 电子工业出版社 / 2016-10 / 89

《游戏编程算法与技巧》介绍了大量今天在游戏行业中用到的算法与技术。《游戏编程算法与技巧》是为广大熟悉面向对象编程以及基础数据结构的游戏开发者所设计的。作者采用了一种独立于平台框架的方法来展示开发,包括2D 和3D 图形学、物理、人工智能、摄像机等多个方面的技术。《游戏编程算法与技巧》中内容几乎兼容所有游戏,无论这些游戏采用何种风格、开发语言和框架。 《游戏编程算法与技巧》的每个概念都是用C#......一起来看看 《游戏编程算法与技巧》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具