(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
这个原则 主要是给子类下了一个确定的定义,也就是说 S
是 T
的 子类,那么在程序中T
能干的说有事情,S
都是能够做的(不违反任何程序性质)。
subtype,(co-,contra-,in)Variance
-
S <: T
,S
是T
的子类,反之亦然 - +A: (co-variance,ps: 这个不用记,记了也根本不知道是什么意思),
class[+A]
类型参数前面,加 ‘+’, 表示,如果A<:B
, 那么class[A] <class[B]
- -A: 与上面相反
- 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
的子类
就如下图所示,读者可自己证明
在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
版权声明
本作品为作者原创文章,采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
作者: taotao
转载请保留此版权声明