原文: Emptiness
做者: Soroush Khanlou
譯者: kemchenjgit
若是 Swift 裏的 array 數組不能爲空?github
仔細想一想: 若是 Swift 已經設計了非空的數組了. 但這會讓人很煩對吧? 什麼語言有非空的數組?數據庫
然而, Swift 比起 C 語言已經修改了不少規則了. 例如, switch 裏不須要 break
了, 甚至可使用 fallthrough
來把幾個 case 鏈接起來. 沒有了 ++
操做符, 它是那麼的讓人迷惑, 多餘, 而且沒了它語言會變得更好.swift
還有一點 Swift 跟 C 不同, Swift 須要顯式地聲明可空性. Swift 讓你使用 Optional
類型, 向類型系統指定某個值是否可能有空. 你能夠說你有一個 controller, 或者可能有一個 controller 也可能沒有. 類型系統能夠在全部地方都檢查一遍, 保證這個值在被須要使用時不會爲空.數組
<!--more-->app
當 Optional
和 Array
產生交匯時, 你會有兩種方式去描述空值: 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 的數組則總會包含至少一個值. 就不可能同時出現兩種語義上的空值了, 而任何採用了別的語義的代碼都不會經過編譯.
非空數組對於創建模型也頗有用處. 告訴類型系統一個給定的數組永遠不可能爲空有時候頗有用. 例如, 也許你的 User
類有許多個郵箱, 但若是 user 沒有郵箱的話則不該該被驗證. 可讓類型系統接收這樣的描述是一件很棒的時期, 但如今咱們作不到. 其餘例子:
一個 Country
國家必須有至少一座 City
城市.
一張 Album
專輯必須有至少一首 Song
歌.
一棟 Building
樓必須有至少一層 Floor
.
這樣的例子一大堆.
若是一個數組類型不能爲空, 這些關係和約束所有均可以在類型系統裏展示出來, 而且你不能刪掉數組裏的最後一個元素.
隨着 Optional
類型的出現, 許多表述都被簡化 當你知道一個類型永遠不可能爲空的時候, 你能夠跳過空值檢查, 用一個更直觀的方式去操做它. 對於非空的數組也是同樣的. 如今, Collection
協議的方法, 例如 first
, last
, max
和 min
都會返回 Optional
, 只是爲了處理數組爲空的狀況.
有許多的狀況下數組都不會爲空, 但每當我使用諸如 first
之類的方法的時候, 我仍是不得不去作防護, 僅僅只是爲了告訴類型系統它不爲空.
若是數組不可能爲空的話, 這些方法均可以返回一個非空值, 使用這些語句都會變得更容易. 空數組能夠經過 optional chaining 來調用這些方法, 而返回值也會是 Optional
.
若是數組不可能爲空, 那往非空數組裏插入內容就能夠很正常地工做. 但往一個可空數組裏插入值就會是一場災難.
var emptiableArray = //... emptiableArray == nil ? emptiableArray = [newItem] : emptiableArray?.append(newItem)
這很讓人心煩, 但好消息是, 在 Swift 3.1 裏, 咱們能夠給特定類型的泛型類型添加 extension. 那麼, 咱們就能夠往 Optional
的 Array
類型添加方法(在這以前, 你只能給使用了遵照了協議的某個類型添加 extension)
extension Optional<Array<Element>> { func append(_ element: Element) { switch self { case .some(array): array.append(element) case .none: self = [element] } } }
如今咱們能夠像以前那樣暢通無阻的操做了.
咱們再進一步, 若是數組的泛型參數包含了數組長度呢? 例如, 給 Array<of: 4, element: String>
插入一個值的時候就會返回一個 Array<of: 5, element: String
. 這個概念被稱爲 dependent types, 而且在一些實驗性的帶有更先進的類型系統的語言裏已經實現了, 例如 Coq, Agda 和 Idris. Oisín 討論過如何在 Swift 裏實現同樣的東西出來.
雖然這些東西很是好玩, 但也有一點不切實際. 你想一想, 這意味着你不能在類裏保存數組了, 除非你知道這個數組的長度永遠不會被改變. 在不少狀況下, 你不可能知道編譯時會有多少個對象從 API 和數據庫裏被返回
簡單的鑑別 空/非空 有很明確的現實意義, 而且也會簡化 Swift 不少內部運做方式.
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
, max
和 min
裏返回 non-optional, 雖然 Collection
裏它們被定義爲 Optional
, repo 裏的測試有斷言來判斷這個.
擁有一個絕對不爲空的數組會有不少有趣的事情發生. 插入還好, 但刪除元素的方法會帶來更多問題. 我把這個方法標記爲 throws
, 但通過更多思考以後, 這也許不是一種正確地作法. 畢竟, Swift 原生的數組刪除元素時也會產生問題, 只是它比起 NonEmptyArray
能夠一個以上的元素. Swift 的數組會在嘗試刪除空數組的元素時調用 fatalError
, 因此也許這纔是正確地作法.
我很期待能夠把 NonEmptyArray
拆分紅幾個提案, 看看失去 Swift 原生數組類型的語法糖是否值得, 去換取返回 non-optional 的方法.