Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"for z in zip()" doesn't terminate #43821

Closed
stevengj opened this issue Jan 15, 2022 · 13 comments · Fixed by #47370
Closed

"for z in zip()" doesn't terminate #43821

stevengj opened this issue Jan 15, 2022 · 13 comments · Fixed by #47370
Labels
iteration Involves iteration or the iteration protocol
Milestone

Comments

@stevengj
Copy link
Member

stevengj commented Jan 15, 2022

for z in zip()
    @show z
end

is an infinite loop, printing z = () over and over.

Arguably, it should execute once with z = (), or maybe throw an error, or maybe execute zero times (like Python), but in any case the current behavior seems bad.

@stevengj stevengj added the iteration Involves iteration or the iteration protocol label Jan 15, 2022
@tkf
Copy link
Member

tkf commented Jan 16, 2022

I'd say that the behavior of zip is correct. Observe that zip is a monoid in the sense that zip(a, zip(b, c)) and zip(zip(a, b), c) are equivalent modulo how the elements are nested in the resulting element tuples (it's possible to write define an equivalence relation ==′ by ignoring the difference between (x, (y, z)) and ((x, y), z)). Furthermore, like other monoids, it defines the vararg method satisfying zip(args...) ==′ reduce(zip, args) when args is nonempty. From this observation, zip() should be the identity element; i.e., zip(zip(), a) ==′ zip(a). An empty stream of ()s satisfies this. Indeed,

julia> for (_, x) in zip(zip(), 1:3)  # this is like `0 + 3`
           @show x
       end
x = 1
x = 2
x = 3

From this point of view, the definition foreach(f) = (f(); nothing) is wrong. It would have been OK if the vararg extension of foreach used product and not zip.

Ref: #35293

@Moelf
Copy link
Sponsor Contributor

Moelf commented Oct 8, 2022

@tkf ehh... why would 99.9% of the users care if zip is a monoid? and did we promise it was a monoid?

in fact there's a single (place) mention of "monoid" in the entire Julia base:

More precisely, the set of all finite-length strings *S* together with the string concatenation operator
`*` forms a [free monoid](https://en.wikipedia.org/wiki/Free_monoid) (*S*, `*`). The identity element
of this set is the empty string, `""`. Whenever a free monoid is not commutative, the operation is

I'm not saying what you're saying is wrong / irrelevant, just asking from a typical user perspective. If you have a user of Julia asking (e.g. a grad student from not CS) "why does empty zip have infinite length", would you give them the above speech?

@rafaqz
Copy link
Contributor

rafaqz commented Oct 26, 2022

Maybe a simpler grad-student ready explanation is "zip() is like a functional while true".

@Moelf
Copy link
Sponsor Contributor

Moelf commented Oct 26, 2022

and what is zip(a,b)?.... this doesn't make sense with the "zip things together" metaphor, zip(something, something) is finite length but zip nothing is infinite length?

@jariji
Copy link
Contributor

jariji commented Oct 27, 2022

zip(xss...) stops when any of its inputs stops. If xss is empty, then none of its inputs ever stops, so the overall expression doesn't terminate.

Adding inputs can only make it stop sooner.

This is the same kind of reasoning that explains

julia> all(!isempty, Vector[])
true

For the opposite behavior, see zip_longest, where adding inputs can only make it stop later.

@LilithHafner
Copy link
Member

If this is a common error, perhaps we can make zip() throw in 2.0, otherwise, I don't think there is much to be done about this.

@LilithHafner LilithHafner added this to the 2.0 milestone Oct 27, 2022
@Moelf
Copy link
Sponsor Contributor

Moelf commented Oct 27, 2022

If xss is empty, then none of its inputs ever stops,

by this logic for x in () or for x in 1:1 should infinite loop

@Moelf
Copy link
Sponsor Contributor

Moelf commented Oct 27, 2022

zip() throw in 2.0,

I'm 99% sure nobody actually relies on this producing infinite loop, we can fix this as a minor change in 1.x

@jariji
Copy link
Contributor

jariji commented Oct 27, 2022

for x in () iterates length(()) times and for x in 1:1 iterates length(1:1) times. I don't see why either would be infinite?

julia> collect(reduce(zip, [1:3, 4:6]; init=zip()))
3-element Vector{Tuple{Tuple{Tuple{}, Int64}, Int64}}:
 (((), 1), 4)
 (((), 2), 5)
 (((), 3), 6)

This code seems fine to me, I wouldn't try to ban it.

@LilithHafner
Copy link
Member

I'm 99% sure nobody actually relies on this producing infinite loop, we can fix this as a minor change in 1.x

Feel free to make a PR; we can check with PkgEval.

@Moelf
Copy link
Sponsor Contributor

Moelf commented Oct 27, 2022

@jariji

zip(xss...) stops when any of its inputs stops

because I would describe iterating 1:1 or () as "inputs stops"

btw the current behavior comes from #29238 with no discussion on this specific point

jariji added a commit to jariji/julia that referenced this issue Oct 28, 2022
`zip(xss...)` terminates when its shortest argument does -- the initial case is infinite.
JuliaLang#43821 (comment)
@jariji
Copy link
Contributor

jariji commented Oct 28, 2022

I've added a PR to document the behavior. This change will reduce the risk of surprise, while retaining the composability.

@Moelf
Copy link
Sponsor Contributor

Moelf commented Oct 29, 2022

Is C++ viewed::zip a zip?

KristofferC pushed a commit that referenced this issue Nov 2, 2022
`zip(xss...)` terminates when its shortest argument does -- the initial case is infinite.
#43821 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
iteration Involves iteration or the iteration protocol
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants