類型系統(tǒng)的首要目的是檢測程序錯(cuò)誤。類型系統(tǒng)有效的提供了一個(gè)靜態(tài)檢測的有限形式,允許我們代碼中明確某種類型的變量并且編譯器可以驗(yàn)證。類型系統(tǒng)當(dāng)然也提供了其他好處,但錯(cuò)誤檢測是他存在的理由(Raison d’être)
我們使用類型系統(tǒng)應(yīng)當(dāng)反映這一目標(biāo),但我們必須考慮到讀者(譯注:讀你代碼的人):明智地使用類型可以增加清晰度,而過份聰明只會(huì)迷亂。
Scala的強(qiáng)大類型系統(tǒng)是學(xué)術(shù)探索和實(shí)踐共同來源(例如Type level programming in Scala) 。但這是一個(gè)迷人的學(xué)術(shù)話題,這些技術(shù)很少在應(yīng)用和正式產(chǎn)品代碼中使用。它們應(yīng)該被避免。
盡管Scala允許返回類型是可以省略的,加上它們提供了很好的文檔:這對public方法特別重要。而當(dāng)一個(gè)方法不需要對外暴露,并且它的返回值類型是顯而易見的時(shí)候,則可以直接省略。
在使用混入(mixin)實(shí)例化對象時(shí)這一點(diǎn)尤其重要,Scala編譯器為這些對象創(chuàng)造了單類。例如:
trait Service
def make() = new Service {
def getId = 123
}
上面的make不需要定義返回類型為Service;編譯器會(huì)創(chuàng)建一個(gè)加工過的類型: Object with Service{def getId:Int}(譯注:with是Scala里的mixin的語法)。若用一個(gè)顯式的注釋:
def make(): Service = new Service{}
現(xiàn)在作者則不必改變make方法的公開類型而隨意的混入(mix in) 更多的特質(zhì)(traits),使向后兼容很容易實(shí)現(xiàn)。
變型(Variance)發(fā)生在泛型與子類型化(subtyping)結(jié)合的時(shí)候。與容器類型的子類型化有關(guān),它們定義了對所包含的類型如何子類型化。因?yàn)镾cala有聲明點(diǎn)變型(declaration site variance)注釋(annotation),公共庫的作者——特別是集合——必須有豐富的注釋器。這些注釋對共享代碼的可用性很重要,但濫用也會(huì)很危險(xiǎn)。
不可變(invariants)是Scala類型系統(tǒng)中高級部分,但也是必須的一面,因?yàn)樗兄谧宇愋突膽?yīng)用,應(yīng)該廣泛(并且正確)地使用。
不可變(Immutable)集合應(yīng)該是協(xié)變的(covariant)。接受容器化類型得方法應(yīng)該適當(dāng)?shù)亟导?downgrade)集合:
trait Collection[+T] {
def add[U >: T](other: U): Collection[U]
}
可變(mutable)集合應(yīng)該是不可變的(invariant). 協(xié)變對于可變集合是典型無效的??紤]:
trait HashSet[+T] {
def add[U >: T](item: U)
}
和下面的類型層級:
trait Mammal
trait Dog extends Mammal
trait Cat extends Mammal
如果我現(xiàn)在有一個(gè)狗(dog)的 HashSet:
val dogs: HashSet[Dog]
把它作為一個(gè)哺乳動(dòng)物的Set,增加一只貓(cat)
val mammals: HashSet[Mammal] = dogs
mammals.add(new Cat{})
這將不再是一個(gè)只存儲(chǔ)狗(dog)的HashSet!
類型別名應(yīng)當(dāng)在其提供了便捷的命名或闡明意圖時(shí)使用,但對于自解釋(不言自明)的類型不要使用類型別名。比如
() => Int
比下面定義的別名IntMarker更清晰
type IntMaker = () => Int
IntMaker
但,下面的別名:
class ConcurrentPool[K, V] {
type Queue = ConcurrentLinkedQueue[V]
type Map = ConcurrentHashMap[K, Queue]
...
}
是有用的,因?yàn)樗磉_(dá)了目的并更加簡短。
當(dāng)使用類型別名的時(shí)候不要使用子類型化(subtyping)
trait SocketFactory extends (SocketAddress => Socket)
SocketFactory 是一個(gè)生產(chǎn)Socket的方法。使用一個(gè)類型別名更好:
type SocketFactory = SocketAddress => Socket
我們現(xiàn)在可以對 SocketFactory類型的值 提供函數(shù)字面量(function literals) ,也可以使用函數(shù)組合:
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
}
注意類型別名不是新類型——他們等價(jià)于在語法上用別名代替了原類型。
隱式轉(zhuǎn)換是類型系統(tǒng)里一個(gè)強(qiáng)大的功能,但應(yīng)當(dāng)謹(jǐn)慎地使用。它們有復(fù)雜的解決規(guī)則, 使得通過簡單的詞法檢查領(lǐng)會(huì)實(shí)際發(fā)生了什么很困難。在下面的場景使用隱式轉(zhuǎn)換是OK的:
如果你發(fā)現(xiàn)自己在用隱式轉(zhuǎn)換,總要問問自己是否不使用這種方式也可以達(dá)到目的。
不要使用隱式轉(zhuǎn)換對兩個(gè)相似的數(shù)據(jù)類型做自動(dòng)轉(zhuǎn)換(例如,把list轉(zhuǎn)換為stream);顯示地做更好,因?yàn)椴煌愋陀胁煌恼Z意,讀者應(yīng)該意識到這些含義。 譯注: 1)一些單詞的意義不同,但翻譯為中文時(shí)可能用的相似的詞語,比如mutable, Immutable 這兩個(gè)翻譯為可變和不可變,它們是指數(shù)據(jù)的可變與不可變。 variance, invariant 也翻譯為 可變和不可變,(variance也翻譯為“變型”),它們是指類型的可變與不可變。variance指支持協(xié)變或逆變的類型,invariant則相反。
更多建議: