天天看點

《Hack與HHVM權威指南》——1.4 Hack的類型系統

hack提供了一系列強有力的方法來描述類型,在php最基本的布爾型、整型、字元串型、數組等類型系統的基礎上,添加了很多新的方式來結合它們,并且使之更富有表現力。

原始類型

這裡有和php一樣的原始類型:bool、int、float、string、array和resource,這些都是合法有效的hack類型标注。

在php中,有為這些類型專門附加的名字,比如boolean、integer、real和double類型,但是這些在hack中都不是合法有效的,上段提及的6種類型是hack類型系統中可以被接受的原始類型。

作為原始類型的有效補充,這裡還有其他兩種類型:num,可以是整型或者浮點數類型;arraykey,可以是整型,也可以是字元串類型。

對象類型

任何類或者接口的名字(内置或者非内置的)都能夠在類型标注中使用。

枚舉類型

枚舉類型在第3章中會更加全面地闡述,這裡我們隻需要知道枚舉類型就是一組常量值的名字。枚舉類型的名字能夠用做類型标注。對應的類型标注的合法值就是這個枚舉裡面的成員。

元組類型

元組是一個把固定數目的不同類型的值進行打包的辦法,元組類型最通常的用途就是從函數中傳回多個值。

元組類型标準的文法非常簡單,就是用括号包覆,用逗号分隔的類型清單(可能在清單中出現任何其他類型,但是void類型不會出現)。建立一個元組類型的文法和建立數組類型的array()文法一樣,差別在于關鍵詞“array”被“tuple”代替,并且不能有鍵名key。

例如,函數将會傳回一個包含整數值和浮點數的元組類型。

元組類型的表現更像一個受限版的數組類型,你不能修改元組類型的key集合,也就是說,你不能添加或者移除對象。你能夠修改一個元組中的值,但是你不能修改它們的類型。你能夠通過數組編号的文法讀出元組内的值,但是更通常的做法是通過清單配置設定把元組類型解包,而不是讀取某個單獨的元素。

從底層上講,元組類型确實就是數組,如果你把一個元組類型傳遞給函數is_array(),這個函數将會傳回true。

混合型(mixed)

mixed意味着包含null在内的hack程式設計語言裡面所允許的任何值。

void

void隻在函數傳回類型中有效,它意味着這個函數什麼也沒有傳回。(在php中,如果一個函數什麼也不傳回的話,那麼事實上傳回了一個null的值。但是在hack中,函數傳回值傳回viod的話會引發一個錯誤。)

void包含在mixed類型中,也就是說,如果一個函數的傳回類型是mixed,那麼它什麼也不傳回是合法的。

this

this隻在方法傳回類型中有效,對于一個空函數來說,this并不是一個有效的傳回類型。這表明這個方法傳回了一個類的對象,該對象的類與這個方法調用時所在對象的類相同。

這種類型标注的目的就是允許在有子類的類上進行鍊式方法調用,鍊式方法調用是個非常有用的技巧,它們看起來是這樣的:

$random = $rng->setseed(1234)->generate();

為了實作寫鍊式調用,題目中的類必須從那些沒有邏輯傳回值的方法傳回$this,就像這樣:

在這個例子中,如果類rng沒有子類,你可以使用rng作為setseed()方法的傳回類型标注,這不會有任何問題,但是當rng有子類的時候,就會出問題了。

在接下來的例子中,類型檢查器将會報告一個錯誤。因為方法setseed()的傳回類型是rng,它認為調用$rng->setseed(1234)将會傳回一個rng類型,并且在rng對象上調用generatespecial()方法是非法的,因為這個方法僅僅定義在子類中。更多$rng變量特别指明的類型(類型檢查器知道這個類型是specialrng)已經丢失了。

傳回類型标注this巧妙地解決了這個問題:

現在,當類型檢查器計算$rng->setseed(1234)調用的傳回類型的時候,this變量标注将告訴它保持箭頭左邊表達式的類型,這就是說,關于generatespecial()的鍊式調用将是合法的。

靜态方法也能夠使用傳回類型this,在這種情況下,它表明它們傳回了一個對象,這個對象的類就是這個方法所在的類,就是說,從函數get_called_class()傳回的類的名字。滿足this類型标注的辦法就是傳回new static():

<code>class parentclass {</code>

// 這裡需要告之類型檢查器 'new static()'是合法的

類型别名

在3.2節中将詳細闡述,類型别名是對已經存在的類型重新命名的好辦法,你可以使用新名字作為類型标注。

shape

shape類型是個非常特别的類型别名,将在3.3節中較長的描述,并且它們的名字也能夠作為類型标注使用。

nullable類型

除void和mixed之外的所有類型都能夠通過問号字首符号标注為可為空的。對于?int的類型标注來說,它能夠是整型也可以是null類型。mixed類型不能夠标注為nullable的原因就是它已經包含了null類型。

callable類型

雖然php允許callable作為一個參數的類型提示,但是hack并不允許。相反,hack提供了更為強大的文法,允許你明确指出不僅僅一個值是可調用的,還包括它作為參數值時是什麼類型的,以及它傳回的是什麼類型。

這個類型的文法是一個關鍵詞function,後面是括号包覆的參數類型清單,再後面是冒号和傳回類型,上述這些再包在一個括号内。這有些像給函數做變量标注的文法;從根本上來說,這就是一個沒有函數名和參數名的函數簽名。在下面的例子中,$callback就是這樣一個函數,它的參數是一個整型和一個字元串類型,傳回字元串類型:

function do_some_work(array $items,

foreach ($items as $index =&gt; $value) {

}

滿足callable類型标注的callable值有四種情況:閉包、函數、執行個體方法和靜态方法。讓我們通過下面的例子加深一下了解:

下面是一個簡單的閉包的例子:

如果要使用一個已經命名的函數作為callable的值,你需要通過一個特别的函數fun()來傳遞函數名,例如:

fun()函數的參數必須是一個單引号的字元串字面量,類型檢查器會自動查找函數名,并且檢測它的參數類型和傳回類型,然後把fun()當成它傳回了一個正确類型的callable的值。

當使用執行個體方法作為callable的值時,你必須通過特殊的函數inst_meth()來傳遞對象及方法名,這點和fun()函數很像,類型檢查器将會查找對應名稱的方法,并且将inst_meth()當成它傳回了正确類型的callable值一樣。再次說明一下,方法名必須是單引号的字元串字面量:

使用靜态方法也非常相似:把類名和方法名通過函數class_meth()傳遞即可。方法名必須是個單引号的字元串字面量。類名可以是個單引号的字元串字面量,或者是hack中特有的::class構造附加在一個非單引号的類名後面。

在運作環境中,classname::class是和'classname'等價的。

這裡還有另外一種辦法來建立一個調用執行個體方法的callable值,這就是meth_call()函數。它将通過調用你傳遞給它的一個執行個體對象上的方法,來建立一個可調用的值。這裡有個局限性,就是,這個方法必須沒有任何參數,這個局限性在未來的版本中将被解除。

與能夠打包一個特定對象和調用它的方法的函數inst_meth()相比,meth_caller()函數與array_map()和array_filter()這樣的工具函數一起使用特别有用。例如:

當然,有一種值在php中是可調用的,但是在hack的類型檢查器中卻不能識别:擁有一個__invoke()方法的對象。這個問題未來将會被改進。

泛型

也被稱作參數化類型,泛型允許一段代碼在同一方式下使用多種不同的類型,并且保證仍然可驗證為類型安全的。最簡單的例子是取代簡單指定一個值是一個數組,你可以通過泛型指定一個字元串組成的數組,或者由一個person類的對象組成的數組等。

泛型是非常強大的工具,這裡僅做簡單了解,我們将在第2章做全面的闡述。

對于本章而言,已經足夠了解泛型數組的文法了。它通常由關鍵詞array開頭,然後緊随着的是一個或者兩個由尖括号包住的類型名稱。如果在尖括号内隻有一個值,那麼這個類型就是數組中value的類型,并且資料的key被認為是int類型。如果尖括号内有兩個類型,那麼第一個就是key的類型,第二個就是value的類型。舉例說明如下:array意味着整型數組的key映射到布爾型的值,而array意味着字元串類型的key映射到整型。尖括号裡面的類型被稱為類型實參。

需要重點說明的是,一個值如果在php中不能被建立,那麼在hack中你也不能建立這個值。php和hack的基礎元素都是一緻的,hack的類型系統僅僅為更有意思的組合和可能值的子集提供了新的表述方式。

更具體地說,請思考下面的代碼:

在函數f()的函數體内,我們說$m是一個mixed類型的變量,而$i是一個int類型的變量,雖然它們實際上存儲的是同樣的東西。

或者想想下面的代碼:

雖然我們說變量$callable是(function(string): ?int)類型, 但是在此情況下,它和其他閉包一樣,仍然是個對象。它并不是一個僅在hack中存在的神奇函數指針,或者其他類似的概念。

一般來說,如果我們表述某個變量是某個類型的話,這個表述是類型檢查器知曉的,但并不是運作環境所知曉的。