Swift 慣用語法
久而久之,Swift 發展出一種別具一格的專用語法——即一組與其他語言相差甚遠的基本慣用語法 (core idioms)。許多來自 Objective-C、Ruby、Java、Python 等等語言的開發者紛紛投向 Swift 的麾下。數日前, Nicholas T Chambers 讓我幫他來磨練這門新習得的語言。他通過將 Ruby 代碼移植為 Swift 的方式,來構建自己基本的編程技能。他所移植的 代碼 是這樣的:
def find_common(collection)
sorted = {}
most = [0,0]
for item in collection do
if not sorted.key? item then
sorted[item] = 0
end
sorted[item] += 1
if most[1] < sorted[item] then
most[0] = item
most[1] = sorted[item]
end
end
return most
end
然后他所嘗試改編而成的 Swift 代碼 為:
func find_common(items: [Int]) -> [Int] {
var sorted = [Int: Int]()
var most = [0, 0]
for item in items {
if sorted[item] == nil {
sorted[item] = 0
}
sorted[item]! += 1
if most[1] < sorted[item]! {
most[0] = item
most[1] = sorted[item]!
}
}
return most
}
除了一些強制解包的代碼外,這兩者之間幾乎沒有任何區別。我對 Ruby 并不是了如指掌,但是這兩段代碼感覺仍然還是 C 語言的風格,并且也一點都不函數化(是函數式編程領域的意思,而不是「無法工作」的意思)。
我知道 Ruby 支持類似 reduce 之類的操作,但是這里我們并沒有看到。當我剛開始學習 Swift 的時候,我做的第一件事情就是將大篇大篇的 Ruby 函數式調用方法用 Swift 實現出來。當然時至今日,仍然有很多諸如 select 、 reject 、 delete_if 、 keep_if 之類的功能仍然等著我用 Playground 實現出來,感覺無窮無盡的樣子。不過講道理,這種做法非常適合學習 Swift。
下面就是經我建議之后重寫的版本:
import Foundation
extension Array where Element: Hashable {
/// Returns most popular member of the array
///
/// - SeeAlso: https://en.wikipedia.org/wiki/Mode_(statistics)
///
func mode() -> (item: Element?, count: Int) {
let countedSet = NSCountedSet(array: self)
let counts = countedSet.objectEnumerator()
.map({ (item: $0 as? Element, count: countedSet.count(for: $0)) })
return counts.reduce((item: nil, count: 0), {
return ($0.count > $1.count) ? $0 : $1
})
}
}
就某些方面而言,這種重構顯然是作弊了,因為我「借用」了 NSCountedSet 的幫助,不過我覺得用 Swift 來編程并不意味著我們必須要將 Foundation 拒之門外。在我看來,使用計數集 (counted set) 正是這段代碼的任務所在:「 假設我們有一個類型隨機的列表(盡管類型是相同的),列表當中的順序是隨機的。那么該如何找到這個列表當中出現次數最多的元素呢? 」。
下面是我關于重構的一些建議和想法:
- 充分利用各種庫 (Leverage Libraries) 。在遷移到 Swift 的時候,您需要考慮 Foundation 以及 Swift Foundation 類型是否會讓您的重構更加方便快捷。這里計數集便是一個很好的例子,因為它本身就可以自行完成全部的成員分組和計數。不過我希望能夠擁有一個原生版本的計數集,這樣就不用操心于令人瘋狂的對象枚舉 ( objectEnumerator ),此外如果沒有指定可哈希元素的時候,代碼仍然可以通過編譯的情況了。
- 擁抱泛型 (Embrace Generics) 。嘗試挑戰一下將示例中的列表換成隨機類型。將列表硬編碼 (hardcoding) 為 Int 并不是個好方法。因此,一旦您意識到要實現的功能需要用于多種類型的時候,請選擇將泛型引入您的解決方案中。
- 必要時考慮使用協議 (Consider Protocols) 。計數集的原生版本不管怎么說也得是 Hashable 的,就如 Swift 原生的 Set 數據類型一樣。這里我添加了一些限制條件,但是它編譯和運行的結果和我們的預期并不一致。
- 活用函數式編程 (Live Functionally) 。所有類似「從列表中找尋某種類型的元素」的操作無不在明示著讓我去使用函數式編程來解決。如果在對列表進行迭代的時候,需要用變量來存儲中間狀態的話,那么可以考慮使用 Swift 最基本的函數式調用操作: map / reduce / filter ,從而消除冗余的顯式狀態 (explicit state)。
- 避免使用全局函數 (Avoid Global Functions) 。我覺得這段代碼最好是用集合擴展的方式進行實現,而不是使用獨立的函數。 mode 函數基本上是對數組進行描述和操作的。這段代碼實現將作為 Array 的一部分存在。我甚至覺得,我應該將其實現為一個屬性,而不是一個函數,因為列表的 mode 功能應該是數組的本質所在 (intrinsic quality)。不過關于這一點,我還有些舉棋不定。
- 別忘了編寫測試以及文檔 (Think Tests and Documents) 。在編寫代碼之前就考慮如何編寫測試用例以及文檔,這已經是 Swift 開發的核心所在。這里我添加了一些相關的文本標記。不過我還沒有添加相關的測試。
- 使用良好的 Swift 語法規范 (Prefer Good Swiftsmanship) 。首先,在我對代碼整體進行思考之前,我陷入了對語法細節的桎梏當中,比如說「使用條件綁定」以及「鍵入變量/盡可能使用字面量」等等。不過隨著我花了大量的心思來思索之后,我對函數式編程的看法發生了改變,但是這并不意味著基本 Swift 語法規范就可以被忽略掉。
世間有很多事情既需要顧全大局,也需要深入細節 (A lot of this falls into the big picture little picture dichotomy)。在學習 Swift 的時候,您可能希望從細節開始學習:學習可空值的原理、學習如何正確的使用可空值、學習如何使用函數式編程等等,從而一直學習到如何創建測試、如何編寫文檔、如何利用協議和泛型。要學的東西實在是紛繁復雜,很難一步登天。
在 這些知識 的基礎之上,又還有基本的 API 用法,這使得學習 Swift 變得更加困難。對于那些剛剛接觸蘋果開發的人而言,即便他們具備了現代編程語言的基礎知識,但是要區分出 Swift 原生類型和 Cocoa 類型的區別并且掌握 Cocoa/Cocoa Touch API 都將是一個重大的挑戰。
正因如此,用「更 Swift 化」的方式來編寫代碼,不僅意味著要使用約定的慣用語法,同時還意味著要記住和使用語言所處平臺的相關特性。我希望計數集(以及其他 Cocoa Foundation 當中沒被 Swift 原生化的類型)能夠成為 Swift 的原生部分。
來自:http://swift.gg/2017/02/17/swift-idioms/