Emptiness 空值語義

原文: Emptiness
做者: Soroush Khanlou
譯者: kemchenjgit

若是 Swift 裏的 array 數組不能爲空?github

仔細想一想: 若是 Swift 已經設計了非空的數組了. 但這會讓人很煩對吧? 什麼語言有非空的數組?數據庫

然而, Swift 比起 C 語言已經修改了不少規則了. 例如, switch 裏不須要 break 了, 甚至可使用 fallthrough 來把幾個 case 鏈接起來. 沒有了 ++ 操做符, 它是那麼的讓人迷惑, 多餘, 而且沒了它語言會變得更好.swift

還有一點 Swift 跟 C 不同, Swift 須要顯式地聲明可空性. Swift 讓你使用 Optional 類型, 向類型系統指定某個值是否可能有空. 你能夠說你有一個 controller, 或者可能有一個 controller 也可能沒有. 類型系統能夠在全部地方都檢查一遍, 保證這個值在被須要使用時不會爲空.數組

<!--more-->app

Doubly Empty

OptionalArray 產生交匯時, 你會有兩種方式去描述空值: nil 或者是空數組.wordpress

這可能會有點繞, 例如, 當你檢查一個數組是否爲 nil 或者爲空數組的時候. 例如, 你想要更好地使用 Swift 裏的 optional chaining 的時候, optionalArray?.isEmpty 卻返回了一個 Optional<Bool>, 一個本質上很讓人迷惑的類型. 若是在 if 判斷句裏使用了這一描述的話, 編譯器會拋出一個編譯錯誤, 由於這是一個 Optional 的布爾值.post

optionalArray == [] 會被編譯, 但會在數組爲 nil 的時候返回 false, 而這並非咱們想要的行爲. 你能夠有這麼幾種方式達到目的, 但很少:測試

if optionalArray == nil || optionalArray == [] {

if let array = optionalArray, array.isEmpty {

if (optionalArray ?? []).isEmpty {

if optionalArray?.isEmpty != false {

if optionalArray?.isEmpty ?? false {

最簡單的方法是記住不要使用 Optional 的數組. 我一直嚴格遵照着這個規則, 保證不會把不一樣類型的"空值"混合到一塊兒. 對於別的"可空"類型我也是這麼作的 - 字典, 字符串, 布爾值和一些別的類型. 不得不去檢查兩種類型的控制檢查是我最不想作的事情.ui

遵照這個規則很容易, 例如說一個類裏的屬性, 但不可能在全部狀況下都遵照這個規則. 例如, 從一個 Optional 的實例那裏獲取一個數組屬性就會成爲一個 Optional 的數組.

let wheels = optionalCar?.wheels // 結果是 [Wheel]?

從一個字典裏面去獲取數組也是同樣.

let wheels = dictionary["wheels"] as? [Wheel]

你不得不去在每個語句後面都加上 ?? [].

咱們剛擺脫了沒法分辨 controller 和可空 controller 的困境. 得到了簡化語句, 減小錯誤和可聲明的能力. 如今卻又趕上了這種窘境.

若是一個數組不能爲空, 那 Optional 的數組就表明了空數組, 非 Optional 的數組則總會包含至少一個值. 就不可能同時出現兩種語義上的空值了, 而任何採用了別的語義的代碼都不會經過編譯.

Modeling

非空數組對於創建模型也頗有用處. 告訴類型系統一個給定的數組永遠不可能爲空有時候頗有用. 例如, 也許你的 User 類有許多個郵箱, 但若是 user 沒有郵箱的話則不該該被驗證. 可讓類型系統接收這樣的描述是一件很棒的時期, 但如今咱們作不到. 其餘例子:

  • 一個 Country 國家必須有至少一座 City 城市.

  • 一張 Album 專輯必須有至少一首 Song 歌.

  • 一棟 Building 樓必須有至少一層 Floor.

這樣的例子一大堆.

若是一個數組類型不能爲空, 這些關係和約束所有均可以在類型系統裏展示出來, 而且你不能刪掉數組裏的最後一個元素.

Expressions 語句表述

隨着 Optional 類型的出現, 許多表述都被簡化 當你知道一個類型永遠不可能爲空的時候, 你能夠跳過空值檢查, 用一個更直觀的方式去操做它. 對於非空的數組也是同樣的. 如今, Collection 協議的方法, 例如 first, last, maxmin 都會返回 Optional, 只是爲了處理數組爲空的狀況.

有許多的狀況下數組都不會爲空, 但每當我使用諸如 first 之類的方法的時候, 我仍是不得不去作防護, 僅僅只是爲了告訴類型系統它不爲空.

若是數組不可能爲空的話, 這些方法均可以返回一個非空值, 使用這些語句都會變得更容易. 空數組能夠經過 optional chaining 來調用這些方法, 而返回值也會是 Optional.

Appending 插入

若是數組不可能爲空, 那往非空數組裏插入內容就能夠很正常地工做. 但往一個可空數組裏插入值就會是一場災難.

var emptiableArray = //...
emptiableArray == nil
    ? emptiableArray = [newItem]
    : emptiableArray?.append(newItem)

這很讓人心煩, 但好消息是, 在 Swift 3.1 裏, 咱們能夠給特定類型的泛型類型添加 extension. 那麼, 咱們就能夠往 OptionalArray 類型添加方法(在這以前, 你只能給使用了遵照了協議的某個類型添加 extension)

extension Optional<Array<Element>> {
    func append(_ element: Element) {
        switch self {
        case .some(array):
            array.append(element)
        case .none:
            self = [element]
        }
    }
}

如今咱們能夠像以前那樣暢通無阻的操做了.

Without Loss Of Generality

咱們再進一步, 若是數組的泛型參數包含了數組長度呢? 例如, 給 Array<of: 4, element: String> 插入一個值的時候就會返回一個 Array<of: 5, element: String. 這個概念被稱爲 dependent types, 而且在一些實驗性的帶有更先進的類型系統的語言裏已經實現了, 例如 Coq, AgdaIdris. Oisín 討論過如何在 Swift 裏實現同樣的東西出來.

雖然這些東西很是好玩, 但也有一點不切實際. 你想一想, 這意味着你不能在類裏保存數組了, 除非你知道這個數組的長度永遠不會被改變. 在不少狀況下, 你不可能知道編譯時會有多少個對象從 API 和數據庫裏被返回

簡單的鑑別 空/非空 有很明確的現實意義, 而且也會簡化 Swift 不少內部運做方式.

NonEmptyArray

This blog post is mostly a thought experiment. But it’s also a regular experiment. To that end, I built a non-empty array type. You can find it on GitHub here. It acts just like an array, but it isn’t emptiable. It conforms to Sequence, Collection, and has == and != methods.

這篇文章更像是一個 Idea 的嘗試. 但這也只是一個常規嘗試. 做爲結尾, 我創建了一個非空數組類型. 你能夠到這裏看源碼, 運做起來就像一個數組, 但不爲空. 遵照 Sequence, Collection 協議而且有 ==!= 方法.

因爲 Swift 的類型系統有一部分我沒能徹底理解, 但儘管如此, 你仍是能夠重寫協議(例如 Collection)裏的方法(例如 first), 而後把 Element? 修改了 Element, Swift 會在調用時爭產工做, 而且使用更加明確的類型, Element. 這意味着 NonEmptyArray 會在 first, last, maxmin 裏返回 non-optional, 雖然 Collection 裏它們被定義爲 Optional, repo 裏的測試有斷言來判斷這個.

擁有一個絕對不爲空的數組會有不少有趣的事情發生. 插入還好, 但刪除元素的方法會帶來更多問題. 我把這個方法標記爲 throws, 但通過更多思考以後, 這也許不是一種正確地作法. 畢竟, Swift 原生的數組刪除元素時也會產生問題, 只是它比起 NonEmptyArray 能夠一個以上的元素. Swift 的數組會在嘗試刪除空數組的元素時調用 fatalError, 因此也許這纔是正確地作法.

我很期待能夠把 NonEmptyArray 拆分紅幾個提案, 看看失去 Swift 原生數組類型的語法糖是否值得, 去換取返回 non-optional 的方法.

相關文章
相關標籤/搜索