?
This document uses PHP Chinese website manual Release
按Pierce的話講:“類型系統(tǒng)是一個語法方法,它們根據(jù)程序計算的值的種類對程序短語進(jìn)行分類,通過分類結(jié)果錯誤行為進(jìn)行自動檢查?!?/p>
類型允許你表示函數(shù)的定義域和值域。例如,從數(shù)學(xué)角度看這個定義:
f: R -> N
它告訴我們函數(shù)“f”是從實數(shù)集到自然數(shù)集的映射。
抽象地說,這就是 具體 類型的準(zhǔn)確定義。類型系統(tǒng)給我們提供了一些更強大的方式來表達(dá)這些集合。
鑒于這些注釋,編譯器可以 靜態(tài)地 (在編譯時)驗證程序是 合理 的。也就是說,如果值(在運行時)不符合程序規(guī)定的約束,編譯將失敗。
一般說來,類型檢查只能保證 不合理 的程序不能編譯通過。它不能保證每一個合理的程序都 可以 編譯通過。
隨著類型系統(tǒng)表達(dá)能力的提高,我們可以生產(chǎn)更可靠的代碼,因為它能夠在我們運行程序之前驗證程序的不變性(當(dāng)然是發(fā)現(xiàn)類型本身的模型bug!)。學(xué)術(shù)界一直很努力地提高類型系統(tǒng)的表現(xiàn)力,包括值依賴(value-dependent)類型!
需要注意的是,所有的類型信息會在編譯時被刪去,因為它已不再需要。這就是所謂的擦除。
Scala強大的類型系統(tǒng)擁有非常豐富的表現(xiàn)力。其主要特性有:
參數(shù)化多態(tài)性 粗略地說,就是泛型編程
(局部)類型推斷 粗略地說,就是為什么你不需要這樣寫代碼val i: Int = 12:
Int
存在量化 粗略地說,為一些沒有名稱的類型進(jìn)行定義
視窗 我們將下周學(xué)習(xí)這些;粗略地說,就是將一種類型的值“強制轉(zhuǎn)換”為另一種類型
多態(tài)性是在不影響靜態(tài)類型豐富性的前提下,用來(給不同類型的值)編寫通用代碼的。
例如,如果沒有參數(shù)化多態(tài)性,一個通用的列表數(shù)據(jù)結(jié)構(gòu)總是看起來像這樣(事實上,它看起來很像使用泛型前的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ū)嘶癁橐幌盗蓄愋娃D(zhuǎn)換(“asInstanceOf[]”),并且會缺乏類型安全的保障(因為這些都是動態(tài)的)。
多態(tài)性是通過指定 類型變量 實現(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中,有一些你想表達(dá)的類型概念“過于泛化”以至于編譯器無法理解。假設(shè)你有一個函數(shù)
def toList[A](a: A) = List(a)
你希望繼續(xù)泛型地使用它:
def foo[A, B](f: A => List[A], b: B) = f(b)
這段代碼不能編譯,因為所有的類型變量只有在調(diào)用上下文中才被固定。即使你“釘住”了類型B
:
def foo[A](f: A => List[A], i: Int) = f(i)
…你也會得到一個類型不匹配的錯誤。
靜態(tài)類型的一個傳統(tǒng)反對意見是,它有大量的語法開銷。Scala通過 類型推斷 來緩解這個問題。
在函數(shù)式編程語言中,類型推斷的經(jīng)典方法是 Hindley Milner算法,它最早是實現(xiàn)在ML中的。
Scala類型推斷系統(tǒng)的實現(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一次分析一個表達(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)行了類型推斷。請注意我們并不需要明確指定返回類型。
Scala的類型系統(tǒng)必須同時解釋類層次和多態(tài)性。類層次結(jié)構(gòu)可以表達(dá)子類關(guān)系。在混合OO和多態(tài)性時,一個核心問題是:如果T’是T一個子類,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)系的真正含義:對一個給定的類型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ù)特質(zhì)的定義就使用了它!
trait Function1 [-T1, +R] extends AnyRef
如果你仔細(xì)從替換的角度思考一下,會發(fā)現(xià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è)你需要一個以Bird
為參數(shù)的函數(shù):
scala> val getTweet: (Bird => String) = // TODO
標(biāo)準(zhǔn)動物庫有一個函數(shù)滿足了你的需求,但它的參數(shù)是Animal
。在大多數(shù)情況下,如果你說“我需要一個___,我有一個___的子類”是可以的。但是,在函數(shù)參數(shù)這里是逆變的。如果你需要一個參數(shù)為Bird
的函數(shù),并且指向一個參數(shù)為Chicken
的函數(shù),那么給它傳入一個Duck
時就會出錯。但指向一個參數(shù)為Animal
的函數(shù)就是可以的:
scala> val getTweet: (Bird => String) = ((a: Animal) => a.sound ) getTweet: Bird => String = <function1>
函數(shù)的返回值類型是協(xié)變的。如果你需要一個返回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é)變的;一個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 res59: List[Animal] = List(Animal@11f8d3a8, Bird@7e1ec70e, Bird@169ea8d2)
注意返回類型是Animal
。
有時候,你并不關(guān)心是否能夠命名一個類型變量,例如:
scala> def count[A](l: List[A]) = l.size count: [A](List[A])Int
這時你可以使用“通配符”取而代之:
scala> def count(l: List[_]) = l.size count: (List[_])Int
這相當(dāng)于是下面代碼的簡寫:
scala> def count(l: List[T forSome { type T }]) = l.size count: (List[T forSome { type T }])Int
注意量化會的結(jié)果會變得非常難以理解:
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ī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)