做者:Ole Begemann,原文連接,原文日期:2018-11-29git
譯者:WAMaker;校對:numbbbbb,BigNerdCoding;定稿:Forelaxgithub
函數式編程語言的一個經常使用範式是把一個列表切分爲頭部(第一個元素)和尾部(其他元素)。在 Haskell 中,x:xs 會匹配非空列表,將頭部綁定給變量 x,尾部綁定給 xs。編程
Swift 不是一門函數式編程語言。既沒有內置的 List
類型,也沒有集合的特定匹配語法。[1]swift
儘管如此,將 Sequence
或 Collection
切分紅頭部和尾部偶爾頗有用。對於集合來講這很容易:api
extension Collection {
var headAndTail: (head: Element, tail: SubSequence)? {
guard let head = first else { return nil }
return (head, dropFirst())
}
}
if let (firstLetter, remainder) = "Hello".headAndTail {
// firstLetter: Character == "H"
// remainder: Substring == "ello"
}
複製代碼
對於序列來講卻很困難,由於它們能夠是單向(single-pass)的:一些序列只能被迭代一次,迭代器會消耗其中的元素。以網絡流爲例,一旦你從緩衝區裏讀取了一個字節,操做系統便將它拋棄了。你沒法讓它從新來一遍。網絡
一個可能的解決方案是 建立一個迭代器 讀取第一個元素,並把當前的迭代器狀態包裹進一個新的 AnySequence 實例:app
extension Sequence {
var headAndTail: (head: Element, tail: AnySequence<Element>)? {
var iterator = makeIterator()
guard let head = iterator.next() else { return nil }
let tail = AnySequence { iterator }
return (head, tail)
}
}
複製代碼
以上代碼可以實現功能,但不是一個好的通用解決方案,尤爲是對知足 Collection
的類型而言。將尾部包進 AnySequence
會是一個 性能殺手,也不可使用合適的集合類型 SubSequence。編程語言
爲了保護集合的 SubSequence
類型,最好給 Collection
和 Sequence
分別寫擴展。(咱們也將會看到,這是 Swift 5 所推崇的方案,這點會在後面談到。)函數式編程
我沒有找到一個通用的方案,可以讓尾部的 SubSequence
類型無缺,也同時能讓單向序列正常工做。很感謝 Dennis Vennink 可以找出一個解決方案並 分享給我。下面是 他的代碼(我略微對格式進行了修改):函數
extension Sequence {
var headAndTail: (head: Element, tail: SubSequence)? {
var first: Element? = nil
let tail = drop(while: { element in
if first == nil {
first = element
return true
} else {
return false
}
})
guard let head = first else {
return nil
}
return (head, tail)
}
}
複製代碼
Dennis 的竅門是調用 Sequence.drop(while:),爲尾部保留了 SubSequence
類型,同時在 drop(while:)
內部「捕獲」了第一個元素。幹得漂亮!
上面的代碼使用 Swift 4.2。在 Swift 5 中因爲序列再也不會有關聯 SubSequence
類型,只存在於集合中(Swift Evolution proposal SE-0234),以上代碼會崩潰。[2]
這個改變有不少優點,但一樣意味着不可能有一種通用的方法可以讓 SubSequence
同時對 Sequence
和 Collection
有效。
相對的,咱們把那個簡單的解決方案添加給 Collection
:
extension Collection {
var headAndTail: (head: Element, tail: SubSequence)? {
guard let head = first else { return nil }
return (head, dropFirst())
}
}
複製代碼
若是咱們須要讓 Sequence
擁有一樣的功能,就須要添加一個獨立的擴展,使用新的 DropWhileSequence
做爲返回類型的尾部:
extension Sequence {
var headAndTail: (head: Element, tail: DropWhileSequence<Self>)? {
var first: Element? = nil
let tail = drop(while: { element in
if first == nil {
first = element
return true
} else {
return false
}
})
guard let head = first else {
return nil
}
return (head, tail)
}
}
複製代碼
(實現和以前的代碼同樣,僅僅改變了返回的類型。)
[1]爲集合添加一種模式匹配結構做爲一個 可行 的特性已經在論壇屢次被 說起。有了它,你能夠像下面這樣將一個有序集合解構成頭部和尾部:
let numbers = 1...10
let [head, tail...] = numbers
// head == 1
// tail == 2...10
複製代碼
在 switch
表達式中會頗有用。
[2]很遺憾咱們被誤導性的名字 Sequence
給束縛住了。要將 Collection.SubSequence
重命名成更合適的 Slice
會形成 嚴重的代碼破壞。
本文由 SwiftGG 翻譯組翻譯,已經得到做者翻譯受權,最新文章請訪問 swift.gg。