Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Minimal working IteratorProtocol / Sequence in Swift 3

I've found it remarkably difficult to find "working documentation" for using Sequence / IteratorProtocol in Swift 3. The few tutorials/articles out there seem to be for older Swift.

Imagine a toy doubly-linked list class called DLList ...

public class Node
    {
    // whatever "thing" you have a group of, this is that "thing"
    }
public class DLList
    {
    // toy linked list class here
    // so this is a group of "node" in this example
    }

I believe the following represents the simplest (?), correct, way to make it that you can, in a word, use DLList in a for structure.

Step 1, make your DLList conform to DLList:Sequence

public class DLList:Sequence
    {
    // toy linked list class here
    
    public func makeIterator() -> DLListIterator
        {
        return DLListIterator(self)
        }
    }

It would seem that all you have to do is add the makeIterator call.

Step 2, write your iterator, conforms to IteratorProtocol

Since the class is DLList, we'll call it DLListIterator. It would seem that

1, you have to have an "init" which basically takes the group class in question

2, you have to have a next call, which must return one of the "things" which are magically related to your group class.

public class DLListIterator:IteratorProtocol
    {
    var dll:DLList  // you must know the group in question
    var pointer:Node?  // you must know where you are
    
    init(_ dll:DLList)
        {
        // so note those two items
        self.dll = dll
        self.pointer = dll.firstOne
        }
    
    public func next() -> Node?
        {
        // return the next one; careful to return nil at end.
        let thisOne = self.pointer
        self.pointer = self.pointer?.nextOne
        return thisOne
        }
    }

This does seem to work perfectly. ie, you can now go

var d:DLList = DLList()
for n in d
 {
 print or whatever n
 }

you can use e = d.filter( {d.item blah} ) and so on - great.

Question - there's a lot of talk about associated types. In part 1, do you somehow explicitly state/add the "associated type"? Even if it's not explicitly required how would you do that explicitly? What the hell is this associated type business?

Question - in part two I'm completely mystified how it "knows" that Node is the "thing" that relates to DLList. Is there a way to make that explicit, or what am I not understanding?

Swiftness Moreover, the whole thing seems not very Swifty. It seems incredible to do all that just to add iterator output. In Swift3 is there a snappier way, for a real class? (Not a silly example like "countdown numbers".)

Final question I cheerfully mention that the above now allows for and .filter. In fact, is my example "complete" - can I now do everything "iterator-wise" with DLList, that one can do normally in Swift - have I perhaps "forgotten some features" or ?? Is there more to do in making DLList a really good iterator?

like image 739
Fattie Avatar asked Oct 31 '16 21:10

Fattie


2 Answers

It all nicely works via type inference (really strong thing in Swift).

E.g. IteratorProtocol has only one requirement, that is next() -> Element? method. Here is what you can see if you just Cmd-click on IteratorProtocol in XCode:

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

So if you declare a type to conform to IteratorProtocol and provide an implementation of some next() -> Foo? then Swift immediately infers that Foo must be an Element.

You can, of course, make an explicit declaration via:

public class DLListIterator: IteratorProtocol {
    public typealias Element = Node

    public func next() -> Element? {
        // ...
    }
}

And, yes, once you implemented both (Sequence and Iterator, that is) you can do everything that other Sequences can do. All this thanks to default protocol implementations.

Whether all this boilerplate that in order to conform to Sequence you need to provide makeIterator(), that in turn has to provide a next(), is Swifty or not.. I think this is something more of opinion-based. Sometimes, you can implement Sequence without going down to implementing IteratorProtocol (e.g. when you implement a wrapper). So, the split does make sense to me.

like image 79
0x416e746f6e Avatar answered Oct 07 '22 17:10

0x416e746f6e


this is my min example

class TestIter: Sequence, IteratorProtocol {
    var mylist:[Int] = [1,2,3,4,5,6]    // contents
    var curPos = 0                      // var for iterator

    func makeIterator() -> TestIter {
        curPos = 0
        return self
    }

    public typealias Element = Int
    func next() -> Element? {
        if curPos < mylist.count {
            let oldPos = curPos
            curPos += 1
            return mylist[oldPos]
        }
        return nil
    }

}

let testIt = TestIter()
for i in testIt {
    print("i:\(i)")
}
like image 5
lbsweek Avatar answered Oct 07 '22 15:10

lbsweek