从迭代语义中剥离出序列

2020-03-14  本文已影响0人  醉看红尘这场梦

有了上一节内容做铺垫,我们知道,必须得再通过一个类型来单独约束序列自身。于是,我们定义了下面这样的protocol

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

分离序列的定义和遍历的动作

很简单,在Sequence里,我们也用Element表示序列中元素的类型,并且,用Iterator表示从序列中读取下一个元素的对象的类型。显然,它要遵从IteratorProtocol,并且,IteratorProtocol.ElementSequence.Element必须是相同的。

接下来,我们还要约束一个方法,用于获取序列的Iterator对象:

protocol Sequence {
    /// ...
    func makeIterator() -> Iterator
}

这样,之前的的Fibonacci就可以定义成这样:

struct Fibonacci: Sequence {
    typealias Element = Int
    func makeIterator() -> FiboIter {
        return FiboIter()
    }
}

然后,我们定义与它配套使用的Iterator

struct FiboIter: IteratorProtocol {
    var state = (0, 1)

    mutating func next() -> Int? {
        let nextNumber = state.0
        self.state = (state.1, state.0 + state.1)

        return nextNumber
    }
}

这部分,和上一节是完全一样的。然后,我们就有了一个统一访问Sequence类型的套路。首先,像这样来定义Sequence

var fibs = Fibonacci()

其次,当我们要逐个访问元素的时候,通过makeIterator来获取访问方式:

var fib1 = fibs.makeIterator()

fib1.next() // Optional(0)
fib1.next() // Optional(1)
fib1.next() // Optional(1)
fib1.next() // Optional(2)

这样,上一节中反复遍历序列的问题也解决了。我们只要调用一次makeIterator,就会生成一个可以重新遍历的Iterator

当然,实际上,我们完全不用自己定义SequenceIteratorProtocol,Swift的标准库里,正是按照我们这两节里的思路,为我们准备好了现成的定义。我们可以把自己定义的这两个protocol注释掉。然后试着直接用for遍历一下fibs

for n in Fibonacci() {
    if (n <= 5) {
        print(n)
    }
    else {
        break
    }
}

执行一下就会看到,它们可以搭配在一起完美工作。

引用类型的Iterator

在我们的例子中,FiboIter是个struct。也就是说,它是值语义的类型。当我们拷贝了FiboIter之后,每一个对象之后就都会按照它们自己的“进度”遍历序列。这是个很符合我们预期的操作:

var fib2 = fib1

fib1.next() // Optional(3)
fib1.next() // Optional(5)
fib2.next() // Optional(3)
fib2.next() // Optional(3)

在上面的例子中可以看到,给fib2赋值的时候,它拷贝了当时的迭代状态。在接下来的调用里,fib1fib2就按照自己的进度进行遍历了。

但是,如果我们希望Iterator有引用类型的语义该怎么办呢?我们要从两个方面来回答这个问题:

一来,如果我们认为Iterator的值语义是符合直觉的,那么引用语义的Iterator背后的含义则是:这个序列的所有Iterator都是一样的。这个假设,是针对类型的使用者而言的;

二来,对于类型的设计者而言,如果要创建一个引用语义的Iterator,想必一定是有他自己独特的原因。为此,最好让他就彻底把这个“有违直觉”的类型隐藏起来;

因此,Swift设计了一个叫做AnyIterator的类型,它是一个Iterator的包装,经过包装之后的Iterator就变成引用语义的了:

var fib3 = AnyIterator(fib2)
var fib4 = fib3

fib3.next() // Optional(8)
fib3.next() // Optional(13)
fib4.next() // Optional(21)
fib4.next() // Optional(34)

现在,结合刚才我们提到的两点考量,你就更能领会所谓Any的含义了。

其实没有那么黑白分明的Iterator和Sequence

当然,无论是值类型还是引用类型的Iterator,终究,我们用两个独立的类型,分别表示了序列本身和逐个访问序列这个动作。不过,至此,你应该明白了,这样的区分完全是为了语义上更清楚,序列是序列,动作是动作;序列中保存的,是序列的值,动作中保存的,是迭代的状态。但你也不能否认,一个遵从IteratorProtocol的类型,完全有可能就是一个Sequence。本来嘛,你无论如何也不能否认,我们在上一节定义定义ones就是一个序列啊。甚至,Swift标准库的绝大多数Iterator类型,也都遵从了Sequence。因此,理解这两个protocol存在的理由至关重要,我们得辩证的看待这个问题。

上一篇下一篇

猜你喜欢

热点阅读