码迷,mamicode.com
首页 > 其他好文 > 详细

Scala 协变 和 逆变

时间:2015-04-10 22:37:45      阅读:141      评论:0      收藏:0      [点我收藏+]

标签:

Scala 协变 和 逆变


在Scala(以及其他许多编程语言)中,函数也是对象,可以使用、定义其他对象的地方,也可以使用、定义函数。Scala中的函数,具有apply方法的类的实例,就可以当做函数来使用。其中apply接受的参数就是函数的参数,而apply的返回值就是函数的返回值。


首先给出一个接受一个参数的函数的泛型定义

trait Function1[-T, +U] {
  def apply(x: T): U
}

这种函数接受一个参数,参数类型为泛型类型T,返回类型为泛型类型U。和其他支持泛型的语言一样,实际定义函数时T和U的类型会被确定下来,不过需要注意的是,这边的T之前有一个“-”,而U之前有一个“+”。


在这里引入关于这个符号的说明,在声明Scala的泛型类型时,“+”表示协变,而“-”表示逆变

  1. C[+T]:如果A是B的子类,那么C[A]是C[B]的子类。>>>>>协变

  2. C[-T]:如果A是B的子类,那么C[B]是C[A]的子类。>>>>>逆变

  3. C[T]:无论A和B是什么关系,C[A]和C[B]没有从属关系。


根据Liskov替换原则,如果A是B的子类,那么能适用于B的所有操作,都适用于A。让我们看看这边Function1的定义,是否满足这样的条件。假设Bird是Animal的子类,那么看看下面两个函数之间是什么关系:

def f1(x: Bird): Animal // instance of Function1[Bird, Animal]

def f2(x: Animal): Bird // instance of Function1[Animal, Bird]

在这里f2的类型是f1的类型的子类。为什么?


我们先看一下参数类型,根据Liskov替换原则,f1能够接受的参数,f2也能接受。在这里f1接受的Bird类型,f2显然可以接受,因为Bird对象可以被当做其父类Animal的对象来使用。


再看返回类型,f1的返回值可以被当做Animal的实例使用,f2的返回值可以被当做Bird的实例使用,当然也可以被当做Animal的实例使用。


所以我们说,函数的参数类型是逆变的,而函数的返回类型是协变的。

函数的参数类型是逆变的,而函数的返回类型是协变的

那么我们在定义Scala类的时候,是不是可以随便指定泛型类型为协变或者逆变呢?答案是否定的。通过上面的例子可以看出,如果将Function1的参数类型定义为协变,或者返回类型定义为逆变,都会违反Liskov替换原则,因此,Scala规定,协变类型只能作为方法的返回类型,而逆变类型只能作为方法的参数类型。类比函数的行为,结合Liskov替换原则,就能发现这样的规定是非常合理的。


这种函数的泛型特性对于函数式编程非常有用。尽管C++的泛型在语法层面上不支持协变与逆变,但在C++11的function<U(T)>中,返回类型U和参数类型T也同样遵循与Scala相同的协变与逆变规则。


注: 里氏替换原则中说,任何基类可以出现的地方,子类一定可以出现。 


===========================END===========================


Scala 协变 和 逆变

标签:

原文地址:http://my.oschina.net/xinxingegeya/blog/398763

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!