Hacker News new | past | comments | ask | show | jobs | submit login

Would you like to explain that further?



Progressing an iterator is a side-effect (a mutation).

Depending how the last iteration is handled, zip(a, b) and zip(b, a) with iterators of different sizes can have different behaviours: if a is shorter than b, a naïve implementation would update b in the zip(b, a) case, but not in the zip(a, b) case.


There is no unique solution to that problem since sometime you want consumption, and sometimes not, for one or more of the parameters.

__builtins__.zip chose to consume iterators in the order they are passed as a parameter, and therefor end up with an assymetric consumption.

That's why itertools.tee exists, so that we can chose case by case.


That reminds me of a lovely paper "How to Add Laziness to a Strict Language Without Even Being Odd" by Wadler and MacQuenn. They use "map" as their running example, but the problem is basically the same.


zip is generally implemented as a functional concept, where the iterators take values from the backing iterable structure "by const &". You can implement a custom mutating zip operation on top of the iterator protocol.


I believe masklinn is talking about whether `zip` consumes an extra element from the longer iterator on the last iteration, when the shorter iterator raises StopIteration. And the behavior might vary depending on the order of the arguments.

    def a():
      yield "a1"

    def b():
      yield "b1"
      yield "b2"

    x,y = a(),b()
    zip(x,y) # => [("a1","b1")]
    next(x) # => raise StopIteration
    next(y) # => "b2"

    x,y = a(),b()
    zip(y,x) # => [("b1","a1")]
    next(x) # => raise StopIteration
    next(y) # => raise StopIteration




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: