I'm trying to fix this bug by overloading prefix(_ maxLength)
for all lazy sequences and collections, but I'm running into weird compiler issues.
I'm using Xcode 9.0 beta 6 (9M214v), but it's also reproducable in all of the latest snapshots for 4.0.
Given the following iterator,...
public struct LazyPrefixIterator <Base: IteratorProtocol>: IteratorProtocol {
public typealias Element = Base.Element
private var baseIterator: Base
private let maxLength: Int
private var taken = 0
internal init (_ baseIterator: Base, _ maxLength: Int) {
precondition(maxLength >= 0, "Can't take a prefix of negative length from an iterator")
self.baseIterator = baseIterator
self.maxLength = maxLength
}
public mutating func next () -> Element? {
if self.taken >= self.maxLength {
return nil
}
self.taken += 1
return self.baseIterator.next()
}
}
...the following sequence,...
public struct LazyPrefixSequence <Base: Sequence>: LazySequenceProtocol {
public typealias Iterator = LazyPrefixIterator<Base.Iterator>
private let baseSequence: Base
private let maxLength: Int
internal init (_ baseSequence: Base, _ maxLength: Int) {
precondition(maxLength >= 0, "Can't take a prefix of negative length from a sequence")
self.baseSequence = baseSequence
self.maxLength = maxLength
}
public func makeIterator() -> Iterator {
return LazyPrefixIterator(self.baseSequence.makeIterator(), self.maxLength)
}
}
...the following collection...
public struct LazyPrefixCollection <Base: Collection>: LazyCollectionProtocol {
public typealias Iterator = LazyPrefixIterator<Base.Iterator>
public typealias Index = Base.Index
public typealias Element = Base.Element
private let baseCollection: Base
private let maxLength: Int
internal init (_ baseCollection: Base, _ maxLength: Int) {
precondition(maxLength >= 0, "Can't take a prefix of negative length from a collection")
self.baseCollection = baseCollection
self.maxLength = maxLength
}
public func makeIterator() -> Iterator {
return LazyPrefixIterator(self.baseCollection.makeIterator(), self.maxLength)
}
public var startIndex: Index {
return self.baseCollection.startIndex
}
public var endIndex: Index {
var maxLength = 0
var index = self.baseCollection.startIndex
let baseCollectionEndIndex = self.baseCollection.endIndex
while maxLength < self.maxLength && index != baseCollectionEndIndex {
index = self.baseCollection.index(after: index)
maxLength += 1
}
return index
}
public func index (after i: Index) -> Index {
precondition(i != self.endIndex, "Can't advance past endIndex")
return self.baseCollection.index(after: i)
}
public subscript (position: Index) -> Element {
precondition(position >= self.startIndex && position < self.endIndex, "Index out of range")
return self.baseCollection[position]
}
}
...and the following overloads (to squash ambiguity issues),...
public extension LazySequence {
func prefix (_ maxLength: Int) -> LazyPrefixSequence<Elements> {
return LazyPrefixSequence(self.elements, maxLength)
}
}
public extension LazySequenceProtocol {
func prefix (_ maxLength: Int) -> LazyPrefixSequence<Self> {
return LazyPrefixSequence(self, maxLength)
}
}
public extension LazyCollection {
func prefix (_ maxLength: Int) -> LazyPrefixCollection<Base> {
return LazyPrefixCollection(self.elements, maxLength)
}
}
public extension LazyCollectionProtocol {
func prefix (_ maxLength: Int) -> LazyPrefixCollection<Self> {
return LazyPrefixCollection(self, maxLength)
}
}
public extension LazyDropWhileBidirectionalCollection {
func prefix (_ maxLength: Int) -> LazyPrefixCollection<LazyDropWhileBidirectionalCollection<Base>> {
return LazyPrefixCollection(self, maxLength)
}
}
public extension LazyPrefixWhileBidirectionalCollection {
func prefix (_ maxLength: Int) -> LazyPrefixCollection<LazyPrefixWhileBidirectionalCollection<Base>> {
return LazyPrefixCollection(self, maxLength)
}
}
public extension LazyRandomAccessCollection {
func prefix (_ maxLength: Int) -> LazyPrefixCollection<LazyRandomAccessCollection<Base>> {
return LazyPrefixCollection(self, maxLength)
}
}
...the following works as expected (each one of these prints true
)...
print(Array(AnySequence(sequence(first: 0, next: {$0 + 1})).lazy.prefix(2)) == [0, 1])
print(Array(sequence(first: 0, next: {$0 + 1}).lazy.drop(while: {_ in false}).prefix(2)) == [0, 1])
print(Array(sequence(first: 0, next: {$0 + 1}).lazy.filter{_ in true}.prefix(2)) == [0, 1])
print(Array(sequence(first: 0, next: {$0 + 1}).lazy.map{$0}.prefix(2)) == [0, 1])
print(Array(sequence(first: 0, next: {$0 + 1}).lazy.prefix(while: {_ in true}).prefix(2)) == [0, 1])
print(Array(sequence(first: 0, next: {$0 + 1}).lazy.prefix(2)) == [0, 1])
print(Array(AnyCollection([0, 1, 2]).lazy.prefix(2)) == [0, 1])
print(Array(([0, 1, 2].lazy as LazyBidirectionalCollection).prefix(2)) == [0, 1])
print(Array([0, 1, 2].lazy.drop(while: {_ in false}).prefix(2)) == [0, 1])
print(Array(([0, 1, 2].lazy.drop(while: {_ in false}) as LazyDropWhileCollection).prefix(2)) == [0, 1])
print(Array([0, 1, 2].lazy.filter{_ in true}.prefix(2)) == [0, 1])
print(Array(([0, 1, 2].lazy.filter{_ in true} as LazyFilterCollection).prefix(2)) == [0, 1])
print(Array(([0, 1, 2].lazy.map{$0} as LazyMapBidirectionalCollection).prefix(2)) == [0, 1])
print(Array(([0, 1, 2].lazy.map{$0} as LazyMapCollection).prefix(2)) == [0, 1])
print(Array([0, 1, 2].lazy.map{$0}.prefix(2)) == [0, 1])
print(Array([0, 1, 2].lazy.prefix(while: {_ in true}).prefix(2)) == [0, 1])
print(Array(([0, 1, 2].lazy.prefix(while: {_ in true}) as LazyPrefixWhileCollection).prefix(2)) == [0, 1])
print(Array([0, 1, 2].lazy.prefix(2)) == [0, 1])
..., but, when chaining the method multiple times on a collection, weird compiler behaviour occurs. The following works with a return type of LazyPrefixCollection<LazyRandomAccessCollection<[Int]>>
:
_ = [0, 1, 2].lazy.prefix(3)
The following works too, with a return type of LazyPrefixCollection<LazyPrefixCollection<LazyRandomAccessCollection<[Int]>>>
:
_ = [0, 1, 2].lazy.prefix(3).prefix(3)
But once we add another method, it hiccups. It tells me that Expression type '()' is ambiguous without more context
:
_ = [0, 1, 2].lazy.prefix(3).prefix(3).prefix(3)
If we add another one it segment faults while type-checking:
_ = [0, 1, 2].lazy.prefix(3).prefix(3).prefix(3).prefix(3)
Of course, creating intermediate variables for each 'step' works:
let a = [0, 1, 2].lazy.prefix(3)
let b = a.prefix(3)
let c = b.prefix(3)
let d = c.prefix(3)
// Etc.
It's also worth noting that it works when we use a sequence instead of a collection:
_ = sequence(first: 0, next: {(e: Int) -> Int in e + 1}).lazy.prefix(3).prefix(3).prefix(3).prefix(3).prefix(3)
Chaining multiple map
s or any of the other methods from the standard library on a collection doesn't cause any issues. The compiler gladly excepts this monstrosity:
_ = [0, 1, 2].lazy.map{$0}.map{$0}.map{$0}.map{$0}.map{$0}.map{$0}
Which makes me believe I'm doing something wrong in my code, particularly in LazyPrefixCollection
.
What could be causing this behaviour?