美文网首页
从迭代语义中剥离出序列

从迭代语义中剥离出序列

作者: 醉看红尘这场梦 | 来源:发表于2020-03-14 13:34 被阅读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存在的理由至关重要,我们得辩证的看待这个问题。

    相关文章

      网友评论

          本文标题:从迭代语义中剥离出序列

          本文链接:https://www.haomeiwen.com/subject/izdyjhtx.html