類型和泛型

2018-02-24 15:48 更新

類型系統(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)該被避免。

返回類型注解(annotation)

盡管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)換

隱式轉(zhuǎn)換是類型系統(tǒng)里一個(gè)強(qiáng)大的功能,但應(yīng)當(dāng)謹(jǐn)慎地使用。它們有復(fù)雜的解決規(guī)則, 使得通過簡單的詞法檢查領(lǐng)會(huì)實(shí)際發(fā)生了什么很困難。在下面的場景使用隱式轉(zhuǎn)換是OK的:

  • 擴(kuò)展或增加一個(gè)Scala風(fēng)格的集合
  • 適配或擴(kuò)展一個(gè)對象(pimp my library模式)(譯注參見:http://www.artima.com/weblogs/viewpost.jsp?thread=179766)
  • 通過提供約束證據(jù)來加強(qiáng)類型安全。
  • 提供了類型的證據(jù) (typeclassing,haskell中的概念,指定義一組函數(shù),其實(shí)現(xiàn)因所給的數(shù)據(jù)類型不同而不同)
  • 用于Manifests (注:Manifest[T]包含類型T的運(yùn)行時(shí)信息)

如果你發(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則相反。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號