類型系統的首要目的是檢測程序錯誤。類型系統有效的提供了一個靜態檢測的有限形式,允許我們代碼中明確某種類型的變量并且編譯器可以驗證。類型系統當然也提供了其他好處,但錯誤檢測是他存在的理由(Raison d’être)
我們使用類型系統應當反映這一目標,但我們必須考慮到讀者(譯注:讀你代碼的人):明智地使用類型可以增加清晰度,而過份聰明只會迷亂。
Scala的強大類型系統是學術探索和實踐共同來源(例如[Type level programming in Scala](http://apocalisp.wordpress.com/2010/06/08/type-level-programming-in-scala/)) 。但這是一個迷人的學術話題,這些技術很少在應用和正式產品代碼中使用。它們應該被避免。
### 返回類型注解(annotation)
盡管Scala允許返回類型是可以省略的,加上它們提供了很好的文檔:這對public方法特別重要。而當一個方法不需要對外暴露,并且它的返回值類型是顯而易見的時候,則可以直接省略。
在使用混入(mixin)實例化對象時這一點尤其重要,Scala編譯器為這些對象創造了單類。例如:
~~~
trait Service
def make() = new Service {
def getId = 123
}
~~~
上面的make*不需要*定義返回類型為Service;編譯器會創建一個加工過的類型: Object with Service{def getId:Int}(譯注:with是Scala里的mixin的語法)。若用一個顯式的注釋:
~~~
def make(): Service = new Service{}
~~~
現在作者則不必改變make方法的公開類型而隨意的混入(mix in) 更多的特質(traits),使向后兼容很容易實現。
### 變型
變型(Variance)發生在泛型與子類型化(subtyping)結合的時候。與容器類型的子類型化有關,它們定義了對所包含的類型如何子類型化。因為Scala有聲明點變型(declaration site variance)注釋(annotation),公共庫的作者——特別是集合——必須有豐富的注釋器。這些注釋對共享代碼的可用性很重要,但濫用也會很危險。
不可變(invariants)是Scala類型系統中高級部分,但也是必須的一面,因為它有助于子類型化的應用,應該廣泛(并且正確)地使用。
*不可變(Immutable)集合應該是協變的(covariant)*。接受容器化類型得方法應該適當地降級(downgrade)集合:
~~~
trait Collection[+T] {
def add[U >: T](other: U): Collection[U]
}
~~~
*可變(mutable)集合應該是不可變的(invariant)*. 協變對于可變集合是典型無效的。考慮:
~~~
trait HashSet[+T] {
def add[U >: T](item: U)
}
~~~
和下面的類型層級:
~~~
trait Mammal
trait Dog extends Mammal
trait Cat extends Mammal
~~~
如果我現在有一個狗(dog)的 HashSet:
~~~
val dogs: HashSet[Dog]
~~~
把它作為一個哺乳動物的Set,增加一只貓(cat)
~~~
val mammals: HashSet[Mammal] = dogs
mammals.add(new Cat{})
~~~
這將不再是一個只存儲狗(dog)的HashSet!
### 類型別名
類型別名應當在其提供了便捷的命名或闡明意圖時使用,但對于自解釋(不言自明)的類型不要使用類型別名。比如
~~~
() => Int
~~~
比下面定義的別名IntMarker更清晰
~~~
type IntMaker = () => Int
IntMaker
~~~
但,下面的別名:
~~~
class ConcurrentPool[K, V] {
type Queue = ConcurrentLinkedQueue[V]
type Map = ConcurrentHashMap[K, Queue]
...
}
~~~
是有用的,因為它表達了目的并更加簡短。
當使用類型別名的時候不要使用子類型化(subtyping)
~~~
trait SocketFactory extends (SocketAddress => Socket)
~~~
SocketFactory 是一個生產Socket的方法。使用一個類型別名更好:
~~~
type SocketFactory = SocketAddress => Socket
~~~
我們現在可以對 SocketFactory類型的值 提供函數字面量(function literals) ,也可以使用函數組合:
~~~
val addrToInet: SocketAddress => Long
val inetToSocket: Long => Socket
val factory: SocketFactory = addrToInet andThen inetToSocket
~~~
類型別名通過用 package object 將名字綁定在頂層:
~~~
package com.twitter
package object net {
type SocketFactory = (SocketAddress) => Socket
}
~~~
注意類型別名不是新類型——他們等價于在語法上用別名代替了原類型。
### 隱式轉換
隱式轉換是類型系統里一個強大的功能,但應當謹慎地使用。它們有復雜的解決規則, 使得通過簡單的詞法檢查領會實際發生了什么很困難。在下面的場景使用隱式轉換是OK的:
* 擴展或增加一個Scala風格的集合
* 適配或擴展一個對象(pimp my library模式)(譯注參見:http://www.artima.com/weblogs/viewpost.jsp?thread=179766)
* 通過提供約束證據來加強類型安全。
* 提供了類型的證據 (typeclassing,haskell中的概念,指定義一組函數,其實現因所給的數據類型不同而不同)
* 用于Manifests (注:Manifest[T]包含類型T的運行時信息)
如果你發現自己在用隱式轉換,總要問問自己是否不使用這種方式也可以達到目的。
不要使用隱式轉換對兩個相似的數據類型做自動轉換(例如,把list轉換為stream);顯示地做更好,因為不同類型有不同的語意,讀者應該意識到這些含義。 譯注: 1)一些單詞的意義不同,但翻譯為中文時可能用的相似的詞語,比如mutable, Immutable 這兩個翻譯為可變和不可變,它們是指數據的可變與不可變。 variance, invariant 也翻譯為 可變和不可變,(variance也翻譯為“變型”),它們是指類型的可變與不可變。variance指支持協變或逆變的類型,invariant則相反。