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.
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.
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?
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.
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)")
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With