鍍金池/ 教程/ Scala/ 類型和多態(tài)基礎(chǔ)
Java 與 Scala
Searchbird
更多的集合
高級(jí)類型
集合
基礎(chǔ)
使用 specs 測(cè)試
簡(jiǎn)單構(gòu)建工具
Scala 并發(fā)編程
Finagle 介紹
類型和多態(tài)基礎(chǔ)
模式匹配與函數(shù)組合
基礎(chǔ)知識(shí)(續(xù))

類型和多態(tài)基礎(chǔ)

什么是靜態(tài)類型?

按 Pierce 的話講:“類型系統(tǒng)是一個(gè)語法方法,它們根據(jù)程序計(jì)算的值的種類對(duì)程序短語進(jìn)行分類,通過分類結(jié)果錯(cuò)誤行為進(jìn)行自動(dòng)檢查?!?/p>

類型允許你表示函數(shù)的定義域和值域。例如,從數(shù)學(xué)角度看這個(gè)定義:

f: R -> N

它告訴我們函數(shù)“f”是從實(shí)數(shù)集到自然數(shù)集的映射。

抽象地說,這就是具體類型的準(zhǔn)確定義。類型系統(tǒng)給我們提供了一些更強(qiáng)大的方式來表達(dá)這些集合。

鑒于這些注釋,編譯器可以靜態(tài)地 (在編譯時(shí))驗(yàn)證程序是合理的。也就是說,如果值(在運(yùn)行時(shí))不符合程序規(guī)定的約束,編譯將失敗。

一般說來,類型檢查只能保證不合理的程序不能編譯通過。它不能保證每一個(gè)合理的程序都可以編譯通過。

隨著類型系統(tǒng)表達(dá)能力的提高,我們可以生產(chǎn)更可靠的代碼,因?yàn)樗軌蛟谖覀冞\(yùn)行程序之前驗(yàn)證程序的不變性(當(dāng)然是發(fā)現(xiàn)類型本身的模型 bug?。?。學(xué)術(shù)界一直很努力地提高類型系統(tǒng)的表現(xiàn)力,包括值依賴(value-dependent)類型!

需要注意的是,所有的類型信息會(huì)在編譯時(shí)被刪去,因?yàn)樗巡辉傩枰_@就是所謂的擦除。

Scala 中的類型

Scala 強(qiáng)大的類型系統(tǒng)擁有非常豐富的表現(xiàn)力。其主要特性有:

  • 參數(shù)化多態(tài)性 粗略地說,就是泛型編程
  • (局部)類型推斷 粗略地說,就是為什么你不需要這樣寫代碼 val i: Int = 12: Int
  • 存在量化 粗略地說,為一些沒有名稱的類型進(jìn)行定義
  • 視窗 我們將下周學(xué)習(xí)這些;粗略地說,就是將一種類型的值“強(qiáng)制轉(zhuǎn)換”為另一種類型

參數(shù)化多態(tài)性

多態(tài)性是在不影響靜態(tài)類型豐富性的前提下,用來(給不同類型的值)編寫通用代碼的。

例如,如果沒有參數(shù)化多態(tài)性,一個(gè)通用的列表數(shù)據(jù)結(jié)構(gòu)總是看起來像這樣(事實(shí)上,它看起來很像使用泛型前的Java):

scala> 2 :: 1 :: "bar" :: "foo" :: Nil
res5: List[Any] = List(2, 1, bar, foo)

現(xiàn)在我們無法恢復(fù)其中成員的任何類型信息。

scala> res5.head
res6: Any = 2

所以我們的應(yīng)用程序?qū)?huì)退化為一系列類型轉(zhuǎn)換(“asInstanceOf[]”),并且會(huì)缺乏類型安全的保障(因?yàn)檫@些都是動(dòng)態(tài)的)。

多態(tài)性是通過指定 類型變量 實(shí)現(xiàn)的。

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

scala> drop1(List(1,2,3))
res1: List[Int] = List(2, 3)

Scala 有秩 1 多態(tài)性

粗略地說,這意味著在 Scala 中,有一些你想表達(dá)的類型概念“過于泛化”以至于編譯器無法理解。假設(shè)你有一個(gè)函數(shù)

def toList[A](a: A) = List(a)

你希望繼續(xù)泛型地使用它:

def foo[A, B](f: A => List[A], b: B) = f(b)

這段代碼不能編譯,因?yàn)樗械念愋妥兞恐挥性谡{(diào)用上下文中才被固定。即使你“釘住”了類型 B:

def foo[A](f: A => List[A], i: Int) = f(i)

…你也會(huì)得到一個(gè)類型不匹配的錯(cuò)誤。

類型推斷

靜態(tài)類型的一個(gè)傳統(tǒng)反對(duì)意見是,它有大量的語法開銷。Scala 通過 類型推斷 來緩解這個(gè)問題。

在函數(shù)式編程語言中,類型推斷的經(jīng)典方法是 Hindley Milner 算法,它最早是實(shí)現(xiàn)在 ML 中的。

Scala 類型推斷系統(tǒng)的實(shí)現(xiàn)稍有不同,但本質(zhì)類似:推斷約束,并試圖統(tǒng)一類型。

例如,在 Scala 中你無法這樣做:

scala> { x => x }
<console>:7: error: missing parameter type
       { x => x }

而在 OCaml 中你可以:

# fun x -> x;;
- : 'a -> 'a = <fun>

在 Scala 中所有類型推斷是 局部的 。Scala 一次分析一個(gè)表達(dá)式。例如:

scala> def id[T](x: T) = x
id: [T](x: T)T

scala> val x = id(322)
x: Int = 322

scala> val x = id("hey")
x: java.lang.String = hey

scala> val x = id(Array(1,2,3,4))
x: Array[Int] = Array(1, 2, 3, 4)

類型信息都保存完好,Scala 編譯器為我們進(jìn)行了類型推斷。請(qǐng)注意我們并不需要明確指定返回類型。

變性 Variance

Scala 的類型系統(tǒng)必須同時(shí)解釋類層次和多態(tài)性。類層次結(jié)構(gòu)可以表達(dá)子類關(guān)系。在混合 OO 和多態(tài)性時(shí),一個(gè)核心問題是:如果 T’T 一個(gè)子類,Container[T’]應(yīng)該被看做是 Container[T] 的子類嗎?變性(Variance)注解允許你表達(dá)類層次結(jié)構(gòu)和多態(tài)類型之間的關(guān)系:

名稱 含義 Scala 標(biāo)記
協(xié)變covariant C[T’]是 C[T] 的子類 [+T]
逆變contravariant C[T] 是 C[T’]的子類 [-T]
不變invariant C[T] 和 C[T’]無關(guān) [T]

子類型關(guān)系的真正含義:對(duì)一個(gè)給定的類型T,如果T’是其子類型,你能替換它嗎?

scala> class Covariant[+A]
defined class Covariant

scala> val cv: Covariant[AnyRef] = new Covariant[String]
cv: Covariant[AnyRef] = Covariant@4035acf6

scala> val cv: Covariant[String] = new Covariant[AnyRef]
<console>:6: 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[AnyRef] = Contravariant@49fa7ba

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

逆變似乎很奇怪。什么時(shí)候才會(huì)用到它呢?令人驚訝的是,函數(shù)特質(zhì)的定義就使用了它!

trait Function1 [-T1, +R] extends AnyRef

如果你仔細(xì)從替換的角度思考一下,會(huì)發(fā)現(xiàn)它是非常合理的。讓我們先定義一個(gè)簡(jiǎn)單的類層次結(jié)構(gòu):

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" }
defined class Chicken

假設(shè)你需要一個(gè)以 Bird 為參數(shù)的函數(shù):

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

標(biāo)準(zhǔn)動(dòng)物庫有一個(gè)函數(shù)滿足了你的需求,但它的參數(shù)是 Animal。在大多數(shù)情況下,如果你說“我需要一個(gè)___,我有一個(gè)___的子類”是可以的。但是,在函數(shù)參數(shù)這里是逆變的。如果你需要一個(gè)接受參數(shù)類型 Bird 的函數(shù)變量,但卻將這個(gè)變量指向了接受參數(shù)類型為 Chicken 的函數(shù),那么給它傳入一個(gè) Duck 時(shí)就會(huì)出錯(cuò)。然而,如果將該變量指向一個(gè)接受參數(shù)類型為 Animal 的函數(shù)就不會(huì)有這種問題:

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

函數(shù)的返回值類型是協(xié)變的。如果你需要一個(gè)返回 Bird 的函數(shù),但指向的函數(shù)返回類型是 Chicken,這當(dāng)然是可以的。

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

邊界

Scala 允許你通過邊界來限制多態(tài)變量。這些邊界表達(dá)了子類型關(guān)系。

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[java.lang.String]

scala> biophony(Seq(new Chicken, new Bird))
res5: Seq[java.lang.String] = List(cluck, call)

類型下界也是支持的,這讓逆變和巧妙協(xié)變的引入得心應(yīng)手。List[+T] 是協(xié)變的;一個(gè) Bird 的列表也是 Animal 的列表。List 定義一個(gè)操作::(elem T)返回一個(gè)加入了 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) 來返回一個(gè)List[B]。請(qǐng)注意B >: T,這指明了類型B為類型T的超類。這個(gè)方法讓我們能夠做正確地處理在一個(gè)List[Bird]前面加一個(gè) Animal 的操作:

scala> new Animal :: flock
res59: List[Animal] = List(Animal@11f8d3a8, Bird@7e1ec70e, Bird@169ea8d2)

注意返回類型是 Animal。

量化

有時(shí)候,你并不關(guān)心是否能夠命名一個(gè)類型變量,例如:

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

這時(shí)你可以使用“通配符”取而代之:

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

這相當(dāng)于是下面代碼的簡(jiǎn)寫:

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

注意量化會(huì)的結(jié)果會(huì)變得非常難以理解:

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

突然,我們失去了類型信息!讓我們細(xì)化代碼看看發(fā)生了什么:

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

我們不能使用 T 因?yàn)轭愋筒辉试S這樣做。

你也可以為通配符類型變量應(yīng)用邊界:

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

scala> hashcodes(Seq(1,2,3))
<console>:7: error: type mismatch;
 found   : Int(1)
 required: AnyRef
Note: primitive types are not implicitly converted to AnyRef.
You can safely force boxing by casting x.asInstanceOf[AnyRef].
       hashcodes(Seq(1,2,3))
                     ^

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

參考 D. R. MacIver 寫的 Scala 中的存在類型