類型和多態基礎

課程內容:java

什麼是靜態類型?它們爲何有用?

按Pierce的話講:「類型系統是一個語法方法,它們根據程序計算的值的種類對程序短語進行分類,經過分類結果錯誤行爲進行自動檢查。」es6

類型容許你表示函數的定義域和值域。例如,從數學角度看這個定義:算法

f: R -> N

它告訴咱們函數「f」是從實數集到天然數集的映射。編程

抽象地說,這就是 具體 類型的準肯定義。類型系統給咱們提供了一些更強大的方式來表達這些集合。安全

鑑於這些註釋,編譯器能夠 靜態地 (在編譯時)驗證程序是 合理 的。也就是說,若是值(在運行時)不符合程序規定的約束,編譯將失敗。數據結構

通常說來,類型檢查只能保證 不合理 的程序不能編譯經過。它不能保證每個合理的程序都 能夠 編譯經過。編程語言

隨着類型系統表達能力的提升,咱們能夠生產更可靠的代碼,由於它可以在咱們運行程序以前驗證程序的不變性(固然是發現類型自己的模型bug!)。學術界一直很努力地提升類型系統的表現力,包括值依賴(value-dependent)類型!ide

須要注意的是,全部的類型信息會在編譯時被刪去,由於它已再也不須要。這就是所謂的擦除。函數式編程

Scala中的類型

Scala強大的類型系統擁有很是豐富的表現力。其主要特性有:函數

  • 參數化多態性 粗略地說,就是泛型編程
  • (局部)類型推斷 粗略地說,就是爲何你不須要這樣寫代碼val i: Int = 12: Int
  • 存在量化 粗略地說,爲一些沒有名稱的類型進行定義
  • 視窗 咱們將下週學習這些;粗略地說,就是將一種類型的值「強制轉換」爲另外一種類型

參數化多態性

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

例如,若是沒有參數化多態性,一個通用的列表數據結構老是看起來像這樣(事實上,它看起來很像使用泛型前的Java):

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

如今咱們沒法恢復其中成員的任何類型信息。

scala> res5.head
res6: Any = 2

因此咱們的應用程序將會退化爲一系列類型轉換(「asInstanceOf[]」),而且會缺少類型安全的保障(由於這些都是動態的)。

多態性是經過指定 類型變量 實現的。

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多態性

粗略地說,這意味着在Scala中,有一些你想表達的類型概念「過於泛化」以致於編譯器沒法理解。假設你有一個函數

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

你但願繼續泛型地使用它:

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

這段代碼不能編譯,由於全部的類型變量只有在調用上下文中才被固定。即便你「釘住」了類型B

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

…你也會獲得一個類型不匹配的錯誤。

類型推斷

靜態類型的一個傳統反對意見是,它有大量的語法開銷。Scala經過 類型推斷 來緩解這個問題。

在函數式編程語言中,類型推斷的經典方法是 Hindley Milner算法,它最先是實如今ML中的。

Scala類型推斷系統的實現稍有不一樣,但本質相似:推斷約束,並試圖統一類型。

例如,在Scala中你沒法這樣作:

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

而在OCaml中你能夠:

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

在Scala中全部類型推斷是 局部的 。Scala一次分析一個表達式。例如:

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編譯器爲咱們進行了類型推斷。請注意咱們並不須要明確指定返回類型。

變性 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@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]
                                     ^

逆變彷佛很奇怪。何時纔會用到它呢?使人驚訝的是,函數特質的定義就使用了它!

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

假設你須要一個以Bird爲參數的函數:

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

scala> biophony(Seq(new Chicken, new Bird))
res5: Seq[java.lang.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
res59: List[Animal] = List(Animal@11f8d3a8, Bird@7e1ec70e, Bird@169ea8d2)

注意返回類型是Animal

量化

有時候,你並不關心是否可以命名一個類型變量,例如:

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

這時你可使用「通配符」取而代之:

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

這至關因而下面代碼的簡寫:

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

注意量化會的結果會變得很是難以理解:

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

忽然,咱們失去了類型信息!讓咱們細化代碼看看發生了什麼:

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

咱們不能使用T由於類型不容許這樣作。

你也能夠爲通配符類型變量應用邊界:

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中的存在類型

相關文章
相關標籤/搜索