(co-,contra-,in-)variance in Scala, what is [+-A]

本文主要总结Scala中关于generic variance(泛型变化, 我也不知道该怎么翻译,以下称 GV),(co-,contra-,in)variance in Scala 的相关知识,什么是 generic variance 呢?我的感觉是一种泛型类型的类型系统,应该和 type system 比较相关,(PL专家就不要嘲笑我了)。比如: List<Integer>List<Object> 的子类,合理吗?cast 行吗?为什么不行?什么样的 Function 是另外一个 function 的subclass?

引入

我们先来看一段简单的代码

Object [] x = new Integer[1];
x[0] = "crash";

这样是能够编译通过的,但是我们知道其实这是错误的,而这种错误最好是在编译期就能即时发现,而不是运行时。

我们来简单分析一下,这段代码出现错误的原因, 其实最大的问题就是在于 第二行 的赋值,x是一个 mutable 的object array,所以天然就是允许这样做的,如果 x是一个 immutable 的对象,那 第一行的 操作完全不会在 runtime 出问题

再来一段代码


List<String> yList = new ArrayList<>();
List<Object> xList = yList;

这段代码是会报错的,然而在某些情况下,我们就是需要一个list 既能够装 string, 又能够装 Integer
。所以,我们接下来探讨什么样的操作能够让我们随心所欲的cast,同时不会出现各种各样的 编译期或运行时的问题。

LSP (Liskov substitution principle)

in a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e. an object of type T may be substituted with any object of a subtype S) without altering any of the desirable properties of the program (correctness, task performed, etc.) ---- wikipedia

这个原则 主要是给子类下了一个确定的定义,也就是说 ST 的 子类,那么在程序中T 能干的说有事情,S 都是能够做的(不违反任何程序性质)。

subtype,(co-,contra-,in)Variance

  1. S <: T, ST 的子类,反之亦然
  2. +A: (co-variance,ps: 这个不用记,记了也根本不知道是什么意思),class[+A] 类型参数前面,加 ‘+’, 表示,如果 A<:B, 那么class[A] <class[B]
  3. -A: 与上面相反
  4. A: invriance, ‘A<:B’,但是’class[A]’ 与 ‘class[B]’ 不具有子类关系

Scala 用上述关系完美的解决了引入中的问题。

Array[A] 在 Scala 中是mutable 的,类型参数是 in-variance,因此 第一段代码不会在编译期通过
List[+A] 是 immutable 因此,第二段代码中的做法完全是ok的,因为list不会再去修改它自己了

接下来探讨 -A 的应用

Function[-P,+R]

在什么情况下,Function f[P1,R1] 会是 Function g[P2,R2] 的子类呢?由, LSP 我们有,如果说 g可以做的事情,f 都能做就行了。那么 g 能干什么呢?

g: P2 => R2

f: P1 => R1

如果,P1 >: P2,R1<:R2, 那么 f 一定是 g 的子类

就如下图所示,读者可自己证明

(co-,contra-,in-)variance in Scala, what is [+-A]

在Scala 中默认 参数类型一定是 -, 而return type 一定是 +

所以

trait MyList[+T] {
  def head:T
  def tail:MyList[T]
  def prepend(ele:T):MyList[T] = new Cons(ele,this)

  override def toString: String = this match {
    case week4.Nil => ""
    case Cons(x,y)=>x.toString+y.toString
  }
}

case class Cons[T](val head:T,val tail: MyList[T]) extends MyList[T]{

}

这段代码 第3行会报错,因为 T是’+’ 类却出现在了 ‘-’ 类的地方,那么怎么改变呢?

def prepend[U :> T](ele:U):MyList[U] = new Cons(ele,this)

这就ok了,且满足类型定义

那么

val x: MyList[Int] = MyList(1,2,3)
val y: x.prepend("s")

y 是什么类型呢?对是 List[AnyVal], 其实 ‘+’ 的意思还可以理解为,类型参数只会往上 cast。

reference

  1. *
  2. FPPiS
  3. LSP

版权声明

本作品为作者原创文章,采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

作者: taotao

转载请保留此版权声明