返回首页 Scala 课堂

Scala课堂:类型和多态类型(二)

这里我们转载 Twitter 的 Scala 课堂,转载的内容基本来自 Twitter 的 Scala 课堂中文翻译,部分有小改动.

变性 Variance

Scala 的类型系统必须同时解释类层次和多态性。类层次结构可以表达子类关系。在混合OO和多态性时,一个核心问题是:如果 T’ 是 T 一个子类, Container[T’] 应该被看做是 Container[T] 的子类吗?变性(Variance)注解允许你表达类层次结构和多态类型之间的关系:

含义 Scala 标记
协变covariant C[T’]是 C[T] 的子类 [+T]
逆变contravariant C[T] 是 C[T’]的子类 [-T]
不变invariant C[T] 和 C[T’]无关 [T]

子类型关系的真正含义:对一个给定的类型 T,如果 T’ 是其子类型,你能替换它吗?


scala> class Covariant[+A]
defined class Covariant

scala> val cv: Covariant[AnyRef] = new Covariant[String]
cv: Covariant[AnyRef] = Covariant@5d0c0c59

scala> val cv: Covariant[String] = new Covariant[AnyRef]
<console>:8: error: type mismatch;
 found   : Covariant[AnyRef]
 required: Covariant[String]
       val cv: Covariant[String] = new Covariant[AnyRef]
scala> class Contravariant[-A]
defined class Contravariant

scala> val cv: Contravariant[String] = new Contravariant[AnyRef]
cv: Contravariant[String] = Contravariant@40738293

scala>  val fail: Contravariant[AnyRef] = new Contravariant[String]
<console>:8: error: type mismatch;
 found   : Contravariant[String]
 required: Contravariant[AnyRef]
        val fail: Contravariant[AnyRef] = new Contravariant[String]

逆变似乎很奇怪。什么时候才会用到它呢?令人惊讶的是,函数特质的定义就使用了它!


trait Function1 [-T1, +R] extends AnyRef

如果你仔细从替换的角度思考一下,会发现它是非常合理的。让我们先定义一个简单的类层次结构:


scala>  class Animal { val sound = "rustle" }
defined class Animal

scala>  class Bird extends Animal { override val sound = "call" }
defined class Bird

scala>  class Chicken extends Bird { override val sound = "cluck" }

假设你需要一个以 Bird 为参数的函数:


val getTweet: (Bird => String) = // TODO

标准动物库有一个函数满足了你的需求,但它的参数是 Animal。在大多数情况下,如果你说“我需要一个,我有一个的子类”是可以的。但是,在函数参数这里是逆变的。如果你需要一个参数为 Bird 的函数,并且指向一个参数为 Chicken 的函数,那么给它传入一个 Duck 时就会出错。但指向一个参数为 Animal 的函数就是可以的:


scala>  val getTweet: (Bird => String) = ((a: Animal) => a.sound )
getTweet: Bird => String = <function1>

函数的返回值类型是协变的。如果你需要一个返回 Bird 的函数,但指向的函数返回类型是 Chicken,这当然是可以的。


scala>  val hatch: (() => Bird) = (() => new Chicken )
hatch: () => Bird = <function0>

边界

Scala 允许你通过 边界 来限制多态变量。这些边界表达了子类型关系


scala>  def cacophony[T](things: Seq[T]) = things map (_.sound)
<console>:7: error: value sound is not a member of type parameter T
        def cacophony[T](things: Seq[T]) = things map (_.sound)
                                                         ^

scala>  def biophony[T <: Animal](things: Seq[T]) = things map (_.sound)
biophony: [T <: Animal](things: Seq[T])Seq[String]

scala>  biophony(Seq(new Chicken, new Bird))
res4: Seq[String] = List(cluck, call)

类型下界也是支持的,这让逆变和巧妙协变的引入得心应手。 List[+T] 是协变的;一个 Bird 的列表也是 Animal 的列表。List 定义一个操作::(elem T)返回一个加入了 elem 的新的 List。新的 List 和原来的列表具有相同的类型:


scala> val flock = List(new Bird, new Bird)
flock: List[Bird] = List(Bird@7e1ec70e, Bird@169ea8d2)

scala> new Chicken :: flock
res53: List[Bird] = List(Chicken@56fbda05, Bird@7e1ec70e, Bird@169ea8d2)

List 同样 定义了::[B >: T](x: B) 来返回一个 List[B] 。请注意 B >: T,这指明了类型 B 为类型T的超类。这个方法让我们能够做正确地处理在一个 List[Bird] 前面加一个 Animal 的操作:


scala> new Animal :: flock
res6: List[Animal] = List(Animal@75be93a7, Bird@5ab69598, Bird@9175caf)

注意返回类型是 Animal。

量化

有时候,你并不关心是否能够命名一个类型变量,例如:


scala>  def count[A](l: List[A]) = l.size
count: [A](l: List[A])Int

这时你可以使用“通配符”取而代之:


scala>  def count(l: List[_]) = l.size
count: (l: List[_])Int

这相当于是下面代码的简写:


scala>  def count(l: List[T forSome { type T }]) = l.size
count: (l: List[T forSome { type T }])Int

注意量化会的结果会变得非常难以理解:


scala> def drop1(l: List[_]) = l.tail
drop1: (l: List[_])List[Any]

突然,我们失去了类型信息!让我们细化代码看看发生了什么:


scala> def drop1(l: List[T forSome { type T }]) = l.tail
drop1: (l: List[T forSome { type T }])List[T forSome { type T }]

我们不能使用T因为类型不允许这样做。 你也可以为通配符类型变量应用边界:


scala>  def hashcodes(l: Seq[_ <: AnyRef]) = l map (_.hashCode)
hashcodes: (l: Seq[_ <: AnyRef])Seq[Int]

scala>  hashcodes(Seq(1,2,3))
<console>:9: error: the result type of an implicit conversion must be more specific than AnyRef
               hashcodes(Seq(1,2,3))
                             ^
<console>:9: error: the result type of an implicit conversion must be more specific than AnyRef
               hashcodes(Seq(1,2,3))
                               ^
<console>:9: error: the result type of an implicit conversion must be more specific than AnyRef
               hashcodes(Seq(1,2,3))
                                 ^

scala>  hashcodes(Seq("one", "two", "three"))
res8: Seq[Int] = List(110182, 115276, 110339486)