According to SE-0398, we can now create types with a variable number of generic type parameters. The document uses ZipSequence
as an example:
In the generic parameter list of a generic type, the
each
keyword declares a generic parameter pack, just like it does in the generic parameter list of a generic function. The types of stored properties can contain pack expansion types, as inlet seq
andvar iter
below.This lets us define the return type of the variadic
zip
function as follows:struct ZipSequence<each S: Sequence>: Sequence { typealias Element = (repeat (each S).Element) let seq: (repeat each S) func makeIterator() -> Iterator { return Iterator(iter: (repeat (each seq).makeIterator())) } struct Iterator: IteratorProtocol { typealias Element = (repeat (each S).Element) var iter: (repeat (each S).Iterator) mutating func next() -> Element? { return ... } } }
It unfortunately does not show how to implement the next
method.
I tried implementing it as:
(repeat (each iter).next()!)
This produces the error:
Cannot use mutating member on immutable value of type 'τ_1_0.Iterator'
I know that I did not handle the case of next
returning nil. Let's just assume that the sequences are all infinite - I just want to get to something that at least compiles first. Handling next
returning nil is the least of my concerns, if I can't even write something that compiles.
How can I implement this next
method?
The fact that each iter
is immutable, regardless of its storage seems to be a compiler limitation, which hopefully will be addressed soon.
The immutability can be circumvented, however, as you already did, just wanted to provide another solution that avoids the extra Bool
property, by using exceptions:
func nextFor<I: IteratorProtocol>(for iterator: I) throws -> (I, I.Element) {
var iterator = iterator
guard let value = iterator.next() else { throw IteratorError.noMoreElements }
return (iterator, value)
}
With the above in place, the next
method can look like this:
mutating func next() -> Element? {
do {
let iterVal = try (repeat nextFor(each iter))
iter = (repeat (each iterVal).0)
return (repeat (each iterVal).1)
} catch {
return nil
}
}
Combining everything together:
struct ZipSequence<each S: Sequence>: Sequence {
typealias Element = (repeat (each S).Element)
let seq: (repeat each S)
func makeIterator() -> Iterator {
return Iterator(iter: (repeat (each seq).makeIterator()))
}
struct Iterator: IteratorProtocol {
typealias Element = (repeat (each S).Element)
var iter: (repeat (each S).Iterator)
mutating func next() -> Element? {
do {
let iterVal = try (repeat nextFor(each iter))
iter = (repeat (each iterVal).0)
return (repeat (each iterVal).1)
} catch {
return nil
}
}
}
}
private func nextFor<I: IteratorProtocol>(_ iterator: I) throws -> (I, I.Element) {
var iterator = iterator
guard let value = iterator.next() else { throw IteratorError.noMoreElements }
return (iterator, value)
}
private enum IteratorError: Error {
case noMoreElements
}
func zip<each S: Sequence>(_ seq: repeat each S) -> ZipSequence<repeat each S> {
ZipSequence(seq: (repeat each seq))
}
Usage example:
let zipped = Array(zip([1, 2, 3], ["a", "b", "c", "d"], [true, false]))
print(zipped)
Output:
[(1, "a", true), (2, "b", false)]