2

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 maps 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?

Dennis Vennink
  • 1,083
  • 1
  • 7
  • 23
  • 1
    [Compiles fine for me](http://swift.sandbox.bluemix.net/#/repl/59b6ad7876a4545b3ee7d26d) (and in Xcode 9 beta 6 too). Although note you're missing `prefix(_:)` overloads on your own `LazyPrefixSequence` & `LazyPrefixCollection` types, so it'll fall back to the standard library's `prefix(_:)` that returns an `AnySequence`. – Hamish Sep 11 '17 at 15:37
  • I think you might be right. Recreating it in a .playground or in the IBM Swift Sandbox brings up another weird bug. An even number of `.prefix`es works, whereas an odd amount of `.prefix`es doesn't compile. – Dennis Vennink Sep 11 '17 at 21:49

1 Answers1

1

Adding overloads for prefix(_ maxLength) on LazyPrefixSequence and LazyPrefixCollection makes all compiler issues go away. The code then becomes the following:

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()
  }
}

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)
  }
}

public extension LazyPrefixSequence where Base.SubSequence: Sequence {
  func prefix (_ maxLength: Int) -> LazyPrefixSequence {
    return LazyPrefixSequence(self.baseSequence, Swift.min(self.maxLength, maxLength))
  }
}

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

    while maxLength < self.maxLength && index != self.baseCollection.endIndex {
      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]
  }

  public func prefix (_ maxLength: Int) -> LazyPrefixCollection {
    return LazyPrefixCollection(self.baseCollection, Swift.min(self.maxLength, maxLength))
  }
}

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)
  }
}

Testing it:

let xs = [0, 1, 2, 3, 4].lazy.prefix(3).prefix(10).prefix(100).prefix(10).prefix(5).prefix(1)
let ys = sequence(first: 0, next: {$0 + 1}).lazy.prefix(3).prefix(10).prefix(100).prefix(10).prefix(5).prefix(1)

print(Array(xs)) // [0]
print(type(of: xs)) // LazyPrefixCollection<LazyRandomAccessCollection<Array<Int>>>

print(Array(ys)) // [0]
print(type(of: ys)) // LazyPrefixSequence<UnfoldSequence<Int, (Optional<Int>, Bool)>>

Feedback appreciated. Especially when it comes to the correct typealiases and where clauses. That stuff still feels like arbitrary voodoo black magic to me; if I don't put in the where Base.SubSequence: Sequence restriction on LazyPrefixSequence then it will ask me for a whole bunch of useless overloads on other methods. Why SubSequence doesn't conform to Sequence makes no sense to me.

Dennis Vennink
  • 1,083
  • 1
  • 7
  • 23