Polarity [blog/lti/polarity]

为了计算关于 $R$ 的具体类型参数,还有一个至关重要的操作:变量代换导致的极性变化。 一个类型变量 $X$ 在另一个类型表达式 $R$ 中的极性,描述了当该变量的类型变大或变小时,整个表达式的类型会如何相应地变化。

  • 协变 (Covariant):若 $R$ 在 $X$ 上是协变的,则 $X$ 的「变大」(成为一个超类型)会导致 $R$ 也「变大」。例如,在 $T \to X$ 中,$X$ 是协变的。若 $\mathbb{Z} \lt: \mathbb{R}$,则 $T \to \mathbb{Z} \lt: T \to \mathbb{R}$。
  • 逆变 (Contravariant):若 $R$ 在 $X$ 上是逆变的,则 $X$ 的「变大」会导致 $R$ 「变小」(成为一个子类型)。例如,在 $X \to T$ 中,$X$ 是逆变的。若 $\mathbb{Z} \lt: \mathbb{R}$,则 $\mathbb{R} \to T \lt: \mathbb{Z} \to T$。
  • 不变 (Invariant):若 $R$ 在 $X$ 上是不变的,则只有当 $X$ 保持不变时,$R$ 才保持不变。任何改变都会导致不可比较的结果。例如,在 $X \to X$ 中,$X$ 是不变的。
  • 常量 (Constant):若 $R$ 在 $X$ 上是常量的,则 $X$ 的变化不会影响 $R$ 的类型。常量类型通常是指那些不包含变量的类型,如基本类型或具体的类。

现在我们对极性的考虑主要集中在函数类型之上, 只需要关注变量在函数类型中的位置即可(在箭头的左边还是右边), 当然,需要考虑到嵌套的函数结构。建议读者在此稍作停顿,考虑一下该算法的设计。 当然你也可以直接展开下面的代码块来查看具体实现。

1. Variance Code [blog/lti/variance_code]

pub fn variance(self : Type, va : Var) -> Variance {
  letrec go = (ty : Type, polarity : Bool) => match ty {
    TyVar(v) if v == va => if polarity { Covariant } else { Contravariant }
    TyVar(_) | TyTop | TyBot => Constant
    TyFun(_, params, ret) => {
      let param_variance = params
        .map(p => go(p, !polarity))
        .fold(init=Constant, combine_variance)
      combine_variance(param_variance, go(ret, polarity))
    }
  }
  and combine_variance = (v1 : Variance, v2 : Variance) => match (v1, v2) {
    (Constant, v) | (v, Constant) => v
    (Covariant, Covariant) | (Contravariant, Contravariant) => v1
    (Covariant, Contravariant) | (Contravariant, Covariant) => Invariant
    (Invariant, _) | (_, Invariant) => Invariant
  }

  go(self, true)
}