许多程序设计语言的类型系统支持子类型。例如,如果Cat是Animal的子类型,那么Cat类型的表达式可用于任何出现Animal类型表达式的地方。所谓的变型(variance)是指如何根据组成类型之间的子类型关系,来确定更复杂的类型之间(例如Cat列表之于Animal列表,回传Cat的函数之于回传Animal的函数…等等)的子类型关系。当我们用类型构造出更复杂的类型,原本类型的子类型性质可能被保持、反转、或忽略───取决于类型构造器的变型性质。例如在C#中:
- IEnumerable<Cat>是IEnumerable<Animal>的子类型,因为类型构造器IEnumerable<T>是协变的(covariant)。注意到复杂类型IEnumerable的子类型关系和其接口中的参数类型是一致的,亦即,参数类型之间的子类型关系被保持住了。
- Action<Cat>是Action<Animal>的超类型,因为类型构造器Action<T>是逆变的(contravariant)。(在此,Action<T>被用来表示一个参数类型为T或sub-T的一级函数)。注意到T的子类型关系在复杂类型Action的封装下是反转的,但是当它被视为函数的参数时其子类型关系是被保持的。
- IList<Cat>或IList<Animal>彼此之间没有子类型关系。因为IList<T>类型构造器是不变的(invariant),所以参数类型之间的子类型关系被忽略了。
编程语言的设计者在制定数组、继承、泛型数据类等的类型规则时,必须将“变型”列入考量。将类型构造器设计成是协变、逆变而非不变的,可以让更多的程序俱备良好的类型。另一方面,程序员经常觉得逆变是不直观的;如果为了避免运行时期错误而精确追踪变型,可能导致复杂的类型规则。为了保持类型系统简单同时允许有用的编程,一个编程语言可能把类型构造器视为不变的,即使它被视为可变也是安全的;或是把类型构造器视为协变的,即使这样可能会违反类型安全。
在一门程序设计语言的类型系统中,一个类型规则或者类型构造器是:
- 协变(covariant),如果它保持了子类型序关系≦。该序关系是:子类型≦基类型。
- 逆变(contravariant),如果它逆转了子类型序关系。
- 不变(invariant),如果上述两种均不适用。
C# |
Java |
Scala |
|
(unsafe at runtime) |
(unsafe at runtime) |
(arrays are invariant by design) Though, there is support for Java’s “covariant” arrays, of course. |
|
(covariance/contravariance) |
Defined by a generic type creator (definition-site). (Restricted to generic interfaces and generic delegates) |
Defined by clients of generic type using wildcards (use-site). |
Defined by a generic type creator (definition-site). Also, there are existential types that cover Java’s wildcards functionality. |
【参见】
Covariance and contravariance http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
http://zh.wikipedia.org/wiki/%E9%A1%9E%E5%9E%8B%E7%B3%BB%E7%B5%B1
Comparing covariance/contravariance rules in C#, Java and Scala
http://www.codeproject.com/Articles/899319/Comparing-covariance-contravariance-rules