协变和逆变

December 12, 2013

题外:事实证明,每次我心血来潮都没好结果。

假设A是B的子类,那么List[A]是List[B]的子类吗?A -> C是B -> C子类吗?协变和逆变讨论的就是高阶类型和子类型组合时的子类关系。

A是B的子类,意味着需要父类B的地方,可以替换成子类A,这在面向对象编程里是很自然的,最明显的表现就是可以把A的实例赋值给一个B变量。

下面用A <: B表示A是B的子类。假设 A <: B,给定一个类型构造子T,则T是:

  • 协变(covariance):T[A] <: T[B]
  • 逆变(contravariance):T[B] <: T[A] (这里A和B的顺序反了,所以是“逆”)
  • 不变(invariance):T[A]和T[B]没有子类关系

函数

问题1:假设 A <: B,(A -> C) 和 (B -> C) 的子类关系是?

问题2:假设 A <: B,(C -> A) 和 (C -> B) 的子类关系是?

只要假设一个应用场景,根据LSP原则进行推导,就能知道函数类型:

  1. 对结果是协变的,或者说结果在函数类型的协变位置或正位置。
  2. 对参数是逆变的,或者说参数在函数类型的逆变位置或负位置。

问题9:什么情况下,((A -> C) -> C) <: ((B -> C) -> C)?

我们也可以假设一个使用场景,然后进行推导,但是这有点复杂了。有一个简单的规则可以推导出每个位置是逆变还是协变位置:

一个位置在整个项中的正负是它所在的每个子项的正负的乘积

好绕,不过看一下例子就能明白,以(A -> B) -> C为例,求A的所在位置的正负:

  1. 在(A -> B)中,A在负位置
  2. 在(A -> B) -> C中,(A -> B) 在负位置
  3. 所以A在(A -> B) -> C中的位置是:负 * 负 = 正

回到问题9,((A -> C) -> C) <: ((B -> C) -> C),A、B又在协变位置(正位置),所以 A <: B。

问题4:什么情况下,((A1 -> B1) -> C1) -> (D1 -> E1)) <: ((A2 -> B2) -> C2) -> (D2 -> E2))

答案和出处都见参考2。

为什么这个规则有效呢?我尝试从实际参数的持有者来理解,没有结果,然后看了点范畴论。

范畴论

协变和逆变来自范畴论里的协变和逆变函子(Functor)。在我们讨论的这个范畴里,对象是类型,态射是子类关系。

例1. 下面这个范畴里有类型A和B,箭头由子类指向父类:

g1 a1 A a2 B a1->a2

类型构造子是Functor,把一个范畴映射到另一个范畴。Covariant functor会保留原范畴间的箭头方向,contravariant functor会把箭头反转。

例2. T是covariant functor,G是contravariant functor,T[A]和G[A]是新范畴里的对象,新范畴里对像也是类型,态射也是子类关系,所以T和G还是自函子(红蓝箭头都是由子类指向父类,红箭头用来突出箭头方向和最初的反了,黑箭头是functor的映射)。

g1 ga1 G[A1] ga2 G[A2] ga1->ga2 a1 A1 ga1->a1 G a2 A2 ga2->a2 G a1->a2 ta1 T[A1] a1->ta1 T ta2 T[A2] a2->ta2 T ta1->ta2

现在考虑函数类型的构造子->,它有两个参数,范畴的积不太好理解,但是我们可以部分调用得到一个functor。-> A表示结果为A的函数,是逆变的,A ->表示参数为A的函数,是协变的。

现在看((A -> B) -> C)->D中的B:

g1 b1 B1 b2 B2 b1->b2 aa1 (A->)[B1] b1->aa1 (A->) aa2 (A->)[B2] b2->aa2 (A->) aa1->aa2 ca1 (->C)[(A->)[B1]] aa1->ca1 (->C) ca2 (->C)[(A->)[B2]] aa2->ca2 (->C) ca1->ca2 da1 (->D)[(->C)[(A->)[B1]]] ca1->da1 (->D) da2 (->D)[(->C)[(A->)[B2]]] ca2->da2 (->D) da1->da2

也就是说,经过一系列的逆变和协变,B在((A -> B) -> C)->D的协变位置,和正负乘积的关系就很明显了。

什么用?

还是那句话,用类型更准确地描述需求。

参考

  1. Covariance and contravariance (computer science)
  2. Lifting a higher order function in Haskell