做者: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。