Implementation of Collection Versions of Zip

Thoughts and further points for discussion:

  • Is this the right model for Index? Does it scale to higher arities?

  • Are the conformances to LazySequenceProtocol and LazyCollectionProtocol correctly implemented?

  • What are the possibilities regarding conformance to RangeReplaceableCollection and MutableCollection?

  • Are there any additional performance gains to be made? Should we override certain default implementations?

  • On the topic of naming the global functions.

    The consensus in the pitch phase was that longest should be part of the name. It is highly descriptive and has good precedence in Python.

    But which ones of these two are more Swifty?

    1. zipLongest(_:_:):

      zipLongest([0, 1, 2], [0])
      
    2. zip(longest:_:):

      zip(longest: [0, 1, 2], [0])
      

    Taking existing symbols from the Standard Library into account, e.g., prefix(_:), prefix(upto:), prefix(through:) and prefix(while:), the second one seems to fall more into line with current naming conventions.

  • Now for the fun part. Which ZipLongest2Collection-specific members should we add? Given:

    let xs = zipLongest([0, 1, 2], [0])
    
    1. A view on the shortest prefix:

      print(Array(xs.prefix))
      // Prints "[(.some(0), .some(0))]"
      
      print(xs.prefix.count)
      // Prints "1"
      

      (Up for bikeshedding.)

    2. A view on the longest suffix:

      print(Array(xs.suffix))
      // Prints "[(.some(1), .none), (.some(2), .none)]"
      
      print(xs.suffix.count)
      // Prints "2"
      

      (Up for bikeshedding.)

    3. Convenience subscript(s) using one (or two) Int offset(s):

      print(xs[0]))
      // Prints "(.some(0), .some(0))"
      
      print(xs[1, 0]))
      // Prints "(.some(1), .some(0))"
      

      In my view, we should provide at least the first version since Index.init is internal.

      It makes sense to put these on Collection. (Since the existing subscript(_:) is O(n) for Collections too.)

    4. (This one might be controversial. A binary version of map(_:) named map(_:_:). It would look something like this:

      let f: (Int?) -> Int = { $0 == .none ? 0 : $0! + 1 }
      let g: (Int?) -> Int = { $0 == .none ? 0 : $0! + 2 }
      
      print(xs.map(f, g)))
      // Prints "[(1, 2), (2, 0), (3, 0)]"
      

      This method will increase expressivity and composibility.)

  • What can we implement similarly in Zip2Collection? How do we make sure the API between both variants is uniform?

Things to do on my end:

  • Finalise the implementation (feedback from the community on the points above would be very helpful).
  • Increase test coverage.
  • Create benchmarks.
  • Write missing documentation.
  • When everything is "done", fold it back into apple/swift#18769.
  • Finalise proposal.