Add Various Zip-Related Types and Operations

Things should go into lazy when there is some risk or significant performance downside to them being lazy instead of eager. For example, lazy.map performs the mapping every time you iterate, which could be very costly on multiple iteration, and also captures and stores the mapping closure, which can be risky if it is stateful.

On the other hand, reversed on a bidirectional collection, for example, does not have a significant laziness downside: the reversing operation is only a simple adaptor over index manipulation (hopefully compiling away to nothing), so if all you want to do is reverse an array, there is no need to do it eagerly.

Since there is no significant downside to zip being lazy, there is no need for it to be restricted only to lazy sequences. It is already available for lazy sequences because those are also just sequences. So it shouldn't need "moving". It is already there. To hide it from users unless they type lazy first seems like harming usability purely for the sake of scratching a consistency itch – an insufficiently good reason, even without the issue that this would also be source breaking and therefore need to clear a very high bar.

Unzip, on the other hand, does have a performance downside to being lazy. If what you actually want is to create two arrays, you would have to take two passes over the collection instead of one. That's the argument from my perspective that it should be eager by default.

On the other hand, if all you want is to unzip one side of the pair and ignore the other (lazily or eagerly), that isn't unzip, it's just map { $0.0 }. This means that your gist can also be written with map, avoiding the creation of additional unnecessary types:

func unzip<C: LazyCollectionProtocol,T,U>(
  _ c: C
) -> (first: LazyMapCollection<C,T>, second: LazyMapCollection<C,U>) 
where C.Element == (T,U) {
    return (c.lazy.map({$0.0}),c.lazy.map({$0.1}))
}
4 Likes