Swift5 如何表达一个最质朴的序列

Sequence是整个Swift标准库中整个集合类型体系的起点,它抽象了一个集合最朴素的形式。有多朴素呢?用文字描述出来就是:仅表示一系列类型相同的元素,而不对这一系列元素的性质有任何额外的约定。例如:是否可以多次遍历,是否可以逆序排列,甚至是否包含有限个元素等都没有约定。它唯一约定了的动作,就是从序列当前位置读取下一个元素。

从定义理解Sequence

了解了Sequence的想法之后,我们直接来看Swift中对应的实现。这部分代码在stdlib/public/core/Sequence.swift里:

public protocol Sequence {
  associatedtype Element
  func makeIterator() -> Iterator
}

实际上,在Sequence.swift开始,有一大段官方的注释解释了这个protocol设计的想法,看过这个系列的内容之后,推荐大家去读一下它们。不过现在,还是顺着我们的思路继续。可以看到,Sequence约定的内容非常简单:

  • Element表示序列中元素的类型;
  • makeIterator则返回一个Iterator对象,所谓的Iterator,可以把它看成一个了解序列内部构造的黑盒子,可以帮助我们用一致的接口获取任何序列的“下一个”元素。它的定义在这里:
public protocol IteratorProtocol {
  associatedtype Element
  mutating func next() -> Element?
}

其中:

  • Element表示这个接口返回的元素类型;
  • next()方法,就是刚才说到的访问序列下一个元素的接口。由于不一定可以从序列中获取到下一个元素,因此,next的返回值是Element?。至于为什么next要约束成一个mutating方法,我们稍后用两个例子来解释。

看到这你可能会想了,Sequence.makeIterator返回的对象中Element的类型,和IteratorProtocol中的Element应该是一样的啊。事实上,的确如此,在Sequence的实现里,Iterator的定义是这样的:

public protocol Sequence {
  associatedtype Iterator : IteratorProtocol
    where Iterator.Element == Element
}

最后,把Sequence完整的定义列出来:

public protocol IteratorProtocol {
  associatedtype Element
  mutating func next() -> Element?
}

public protocol Sequence {
  associatedtype Iterator : IteratorProtocol
    where Iterator.Element == Element
  func makeIterator() -> Iterator
}

这就是Swift中,最朴实的一类集合的约定。对于一个遵从了Sequence的类型,就像我们在一开始说的一样,我们不知道它包含的元素个数、不知道它是否可以反复遍历、不知道它是否可以逆序。我们能做的,就是通过它的Iterator获取集合中的下一个元素,而这也是我们唯一可以做的事情。

尽管Swift还通过extensionSequence添加了很多方法,但它们并不是Sequence约束的一部分。也就是说,当自定义一个遵从了Sequence的类型时,不用重新实现它们。

三种不同性质的Sequence

接下来,为了更具象的了解Sequence类型的“质朴”,我们来看三个具体的例子。第一个例子,是我们最容易理解的数组。把它“降维”理解成一个序列完全没问题:

let array = [1, 2, 3, 4, 5, 6]

对于array表示的这个数字序列来说,如果不把它当数组看,我们访问它的元素就只能依靠不断调用Array.Iteratornext方法:

var ia = arr.makeIterator()

while let item = ia.next()  {
  print(item)
}

显然,这种从数组“降维”而来的序列有有限个元素并且支持重复遍历。

第二个例子,Sequence表示的序列也可以是包含无限个元素的(一直到序列中元素值溢出),例如一个Fibonacci数列。为了表示这样的类型,我们先定义对应的Iterator

struct FibonacciIterator: IteratorProtocol {
  // typealias int = Element
  var state = (curr: 0, next: 1)

  mutating func next() -> Int? {
    let curr = state.curr
    state = (curr: state.next, next: state.curr + state.next)

    return curr
  }
}

然后再来定义数列自身:

struct Fibonacci: Sequence {
  // typealias int = Element
  // typealias Iterator = FibonacciIterator

  func makeIterator() -> FibonacciIterator {
    return FibonacciIterator()
  }
}

在注释里,我分别列出了FibonacciIteratorFibonacci中对应的associatedtype类型,为的是帮助我们理解它的定义。实际上这是不必要的,Swift可以根据makeIterator以及next的返回值推导出它们。

现在,思考一个问题,该如何表达一个Fibonacci数列呢?我给你下面两个答案,你选哪个:

/// A
var fibA = FibonacciIterator()

/// B
var fibB = Fibonacci()

是A还是B呢?其实答案是无论哪个都可以。对于fibA来说,我们的侧重点是事实,毕竟只要像下面这样就可以得到数列了:

fibA.next()
fibA.next()
fibA.next()
/// ...

而对于fibB来说,我们的侧重点是形式,Fibonacci数列是一个Sequence。按照约定,为了访问Sequence的每一个成员,我们要获取它的专属Iterator,然后通过next方法完成:

var iterB = fibB.makeIterator()
iterB.next()
iterB.next()
iterB.next()
/// ...

说到这,我们再来回答一个问题,既然FibonacciIterator自身就可以表达一个序列,为什么还要单独定义Fibonacci呢?或者说,为什么要分开IteratorSequence呢?

这个问题的答案和两个因素相关,它们是:Sequence的值和Iterator的状态。通常,Iterator为了可以持续遍历序列,它在内部都要保存一个状态。当这个状态和Sequence要表达的值一致的时候,IteratorSequence的界限就会变得模糊,甚至可以说Iterator自身就表示了这个序列。为了看到这个效果,我们可以直接让FibonacciIterator遵从Sequence

struct FibonacciIterator: IteratorProtocol, Sequence {
  /// The same as before
  /// ...
}

这样,FibonacciIterator就“名正言顺”的成为了一个序列,我们也不用为它专门编写makeIterator方法。这是因为,在Sequence代码里,Swift官方已经为这种情况提供了一个默认实现:

/// A default makeIterator() function for `IteratorProtocol` instances that
/// are declared to conform to `Sequence`
extension Sequence where Self.Iterator == Self {
  /// Returns an iterator over the elements of this sequence.
  @inlinable
  public __consuming func makeIterator() -> Self {
    return self
  }
}

但绝大多数时候,Iterator保持的状态,和Sequence的值,并不是一致的。这时,就必须通过独立的类型来表达IteratorSequence了,而这就是我们要演示的第三类Sequence。这次,我们定义一个由用户输入的每一行内容形成的序列。

先来定义Iterator

struct InputStreamIterator: IteratorProtocol {
  var currLine = 0

  mutating func next() -> String? {
    guard let input = readLine() else { return nil }

    currLine += 1
    print("(\(currLine)) \(input)")

    return input
  }
}

这次,Iterator中用于维系遍历的状态,是currLine,表示这是当前第几行输入,然后在控制台打印类似(1) Hello world这种形式的字符串,并返回当前用户输入的内容。

接下来,定义表示这个序列的类型:

struct InputStream: Sequence {
  func makeIterator() -> InputStreamIterator {
    return InputStreamIterator()
  }
}

最后,用下面的代码来测试:

let stdin = InputStream()
var stdinIter = stdin.makeIterator()

while true {
  guard let _ = stdinIter.next() else { break }
}

把这些代码保存在demo.swift里,并执行swiftc demo.swift -o demo。编译完成后,执行demo,随着在控制台中一行行输入内容,就可以看到类似下面这样的结果了:

ha
(1) ha
haha
(2) haha
hahaha
(3) hahaha
hahahaha
(4) hahahaha

通过这三个例子,我们应该可以彻底明白Sequence要表达的概念了。对于一系列类型相同的序列来说:

  • 它的值可以是预分配的,例如数组中的元素。也可以是lazy的,例如Fibonacci中的访问时现算,或者InputStream中用户的当前输入;
  • 它的元素个数可以是有限的(array),也可以是无限的(Fibonacci / InputStream);
  • 它可以被反复遍历(array / Fibonacci),或者只能遍历一次(InputStream);

你可能感兴趣的:(Swift5 如何表达一个最质朴的序列)