elasticsearch入門系列">elasticsearch入門系列
882
2022-05-30
問題與解決方案
傳統編程語言中往往使用空值(null 或者 None、nil 等)來表達可選值,可謂簡單粗暴。
因為這樣一來,就需要在每一處使用的地方判斷相應的值是否為空,一旦疏忽大意就可能導致程序出錯甚至崩潰。不僅如此,正如著名的《十億美元的錯誤》與《計算機科學中的最嚴重錯誤》所說,傳統空值還引入了一系列其他問題:破壞了類型系統、易與空容器混為一談、表意模凌兩可、難以調試、不便同語言的其他特性結合使用等。
因此,現代編程語言基本都會避免使用傳統空值,而采用更安全的方式來表達可選值,具體方式主要有兩種:
受限的空值:Kotlin、Swift、Hack 等。
可選值類型:Haskell、Rust、Julia、OCaml、Swift、F#、Scala、Java 8+、C++17+等。
你沒看錯,Swift 在兩邊都站隊了。這倒并不是它采用了兩種不同的機制,相反,它在一致的底層機制基礎上,同時兼容兩種上層語法。
另外 F#、Scala、Java 8+、C++ 17+ 實際上處于灰色地帶,它們雖然推薦使用可選值類型,卻也支持傳統空值。
值得一提的是,無論采用哪種方式,其安全性都是由類型系統來保障的。 雖然這并不僅限于靜態類型語言(Hack 與 Julia 都是動態類型語言),不過確實需要一定程度的靜態類型支持,這也從側面反映了現代編程語言的靜態類型趨勢。
歷史包袱
采用受限空值的語言可能都與歷史包袱有關:Kotlin 中有 null 因為 Java 中有,Hack 中有 null 因為 PHP 中有,Swift 中有 nil 因為 Objective-C 中有。
處于灰色地帶的 F#、Scala、Java 8+、C++ 17+ 同樣有歷史包袱,因為需要與 .Net/JVM 平臺的其他語言互操作或者要兼容本語言的舊版本。
但是兩隊語言做了不同的抉擇:一隊采用受限的空值取代了傳統空值;另一隊引入了可選值類型的同時,卻還保留傳統空值。于是后面這隊語言雖有安全的方式,卻也無法擺脫傳統空值的糾纏。Java 8 與 C++ 17 為了兼容歷史版本或是無奈之選,但是如果歷史重新給 F# 與 Scala 選擇機會的話,會不會采用 Swift 的方案更好一些?
受限的空值
在采用受限的空值來表達可選值的編程語言中,對空值的使用有以下限制: 1. 語言中嚴格區分可空類型與非空類型,不能直接將可空值用于只接受非空值的地方。 2. 語言中通過特定語法訪問可空類型對象的成員,也需要特定語法由可空值得到非空值。
區分可空與非空類型
Kotlin、Swift、Hack 都嚴格區分可空類型與非空類型,并且類型都默認非空,對于可空類型也都采用加注 ? 的方式來表達(只是 Hack 放在類型名前,Kotlin 與 Swift 放在類型名后)。我們看些具體的示例:
Swift 版與之非常類似,只需將 val 替換為 let、null 替換為 nil 即可。Hack 語法與它們差異大些、并且需要采用函數形式來表達上述示例,但其相似性還是很明顯的:
除了變量聲明與賦值、函數返回值之外,三門語言對函數傳參、數學運算等各種表達式都會嚴格區分可空與非空類型。
可空性傳播
在采用受限空值的編程語言中,無法直接訪問可空類型對象的成員,需要使用特殊語法。在 Kotlin 與 Swift 中使用 ?. 語法,在 Hack 中使用 ?-> 語法。例如輸出一個可空字符串的長度:
Kotlin 代碼,輸出是 12:
Swift 代碼,輸出是 Optional(12):
Hack 代碼有些復雜,因為內置字符串值不是對象,所以需要模擬出一個對象,其輸出是 12:
Swift 輸出的是 Optional(12),它明確表明這是一個 Int? 值。Kotlin 與 Swift 雖然直接輸出了數字,但其值同樣是可空整型,不能用于只接受非空整數的地方。?./?->的求值邏輯為: 1. 如果對象非空,那么訪問相應成員。 2. 如果對象為空,返回空。 3. 返回類型為可空類型。
以 Kotlin 為例,雖然 String 的 length 屬性是非空成員,但因為 hello 是可空的,進而導致 hello?.length 也是可空的,因此如需繼續調用 plus 也要使用 ?. 語法:
并且這一表達式依然是可空的,因此如果還有后續成員訪問,就還需使用?.語法:
可空性就像病毒一樣感染了整個調用鏈條,并且會繼續傳播下去。在 Hack 中與此類似,只是使用?->語法。Swift 的語法與它們不同,對于上述情況,后續鏈條中用. 即可,但是最終結果仍然是可空值:
Swift 中只有后續成員的返回值本身也是可空類型時才需要再次使用 ?.,參見其官網介紹:
當然,在 Kotlin 中也可以通過高階函數 let 來簡化多級 ?. 的語法,進而達到接近 Swift 的效果:
可空值用于常規函數
如果不是訪問成員,而是用于普通函數,例如將上述鏈式調用的結果傳給 sin 函數并輸出其結果,該如何實現呢?這在 Kotlin 與 Swift 中分別有不同的語法:
Kotlin 代碼:
Swift 代碼:
目前在 Hack 中沒有類似語法,可以通過更通用的由可空表達式獲得非空值的方式實現。
由可空表達式獲得非空值
這里只討論安全獲得非空值的方式。由可空表達式安全地獲得非空值還需要提供一個默認值,這樣就一定能夠取得非空值:當表達式求值結果非空時取求值結果,否則取默認值。這在 Kotlin 中通過 Elvis 操作符(?:)來實現,在 Swift 與 Hack 中通過空接合操作符(??)來實現。
現在有沒有覺得受限空值與問號(?)結下了不解之緣 :P
Kotlin 示例:
Swift 示例:
可選值類型
更多的現代編程語言都是采用可選值類型的方式。在這些語言中,都是通過一種專門的包裝類型來表達可選值。下表列舉了一些語言中可選字符串的類型以及有無值的字面值表示法:
包裝后的類型與原類型明顯不同,因此無法當作原類型來用。那么應該如何使用呢?
顯式判斷與模式匹配
最簡單的使用方式是顯式判斷,例如求一個可選字符串的長度(Julia):
如果用 C++ 或者 Java 實現會與之非常相似。而對于上文所列的其他使用可選值類型的語言,都可以采用模式匹配的方式實現類似功能。例如(Haskell):
在 Haskell 中只需在聲明函數時對 Maybe String 類型參數的不同模式 Nothing 與 Just s 分別編寫實現即可。函數調用時 Haskell 會根據實參類型自動匹配到相應實現。
實際上,Julia 雖然沒有在語言級支持完整的模式匹配,但是在 Julia 中可以通過泛型函數實現與上述 Haskell 代碼類似的寫法:
我們再看一下 Rust 的寫法:
這段代碼乍一看跟傳統語言的 switch-case 很類似,但實際上要強大的多。上述代碼中的 Some(ref s) => 含 s 的表達式 就是傳統 switch-case 無法支持的。對于匹配到模式 Some(ref s) 的 Option,Rust 能夠自動提取模式中對應的 s,并用于后續處理。
我們可以通過顯式判斷或模式匹配來處理可選值類型,但通常并不這么做,因為還有更便捷的方式。
函數式方式
以函數式方式實現求一個可選字符串的長度的代碼,可以這樣寫(Java):
這里用到了 Optional
其中 Optional
Optional.orElse() 接受一個 U 類型的參數 default,如果可選值有值則返回其值,如果無值返回 default,因此通過 Optional.orElse() 總能得到一個 U 類型的值。
實際上,可選值類型是 Functor、Applicative、Monad,上述 Optional.map() 相當于 Haskell 中 Functor 的 fmap/<$>。此外,常用的還有相當于 Monad 的 >>= 的函數,如 Java 的 Optional.flatMap()、Rust 的 Option::and_then()、F# 的 Option.bind 等。我們看一個 F# 的示例——對一個整數可選值求余:
示例中首先定義了一個安全求余運算符 %?,當除數為 0 時它返回 None,否則返回 Some 余數。 %? 只接受整數作除數,如需將其應用到 int option 可以借助 Option.map,但是這樣得到的結果是嵌套的 option(即 int option option)。 有沒有可能直接得到單層的 int option 呢?——這就需要 Option.bind 大顯身手了,如例中所示。
注:上文所列的其他使用可選值類型的語言的標準庫沒有為可選值類型實現類似 Haskell 中 Applicative 的 liftA2/<*> 的函數,可選用第三方實現或者參考 Applicative 文檔或 Kotlin 版圖解 Functor、Applicative 與 Monad 自行實現。
綜合示例
我們看一個 Kotlin 心印中的示例:實現一個給客戶發消息的函數,其中客戶、消息、客戶的個人信息字段、個人信息的郵箱地址字段都可能無值。用傳統 Java 代碼實現如下所示:
上述代碼使用了衛語句,可以說已經是質量很高的傳統 Java 代碼了。但由于對其中每處可空值都需要判空,有意義的代碼與判空代碼各有三行,可以說一半的代碼都浪費在了毫無業務價值而又不得不做的事情上了。同時該代碼中有 4 個分支、4 個出口,代碼雖不多,流程卻已略顯復雜。而如果使用 Java 8 的 Optional,就可以流暢很多:
在 Swift 語言中,可以綜合使用受限空值與可選值類型的語法,代碼會更簡潔一些:
在 Kotlin 語言中,可以綜合使用受限空值與 return 表達式,代碼會非常簡潔:
綜述
傳統空值會帶來一系列問題,為避免這些問題,現代編程語言通常采用受限的空值或者可選值類型來表達可選值。這些現代編程語言不僅通過類型系統確保了可選值的安全性,還提供了各種相對便利的使用方式來提升可選值的易用性。
這里的可選值(optional value)是指可能無值也可能有一個值的情況,在一些編程語言中稱為可空值(nullable value)。
本文轉載自異步社區。
Java 安全
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。