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

Added tests for Iterators to cover a few edge cases. #35916

Merged
merged 3 commits into from
May 9, 2022

Conversation

ravibitsgoa
Copy link
Contributor

Improves test coverage for Iterators.
Covers a few edge cases.

@@ -100,6 +100,7 @@ end
@test length(zip(cycle(1:3), 1:7, cycle(1:3))) == 7
@test length(zip(1:3,product(1:7,cycle(1:3)))) == 3
@test length(zip(1:3,product(1:7,cycle(1:3)),8)) == 1
@test_throws ArgumentError length(zip()) # length of zip of empty tuple
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, length should be extended to return 0 in this case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think length of empty zip should be infinite. This PR also tests that Base.IteratorSize(zip()) == Base.IsInfinite() holds. Also, for x in zip(); end is an infinite loop (which, I think, is correct).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should zip() be infinite?

If I collect/group/zip elements from no collections, I don’t expect there to be any elements, i.e. isempty(collect(zip())) == isempty(zip()) == true. Just like I would expect zip(xs...) = map(Tuple, xs...) and that should have the length of the shortest iterable among xs..., which I think should represent the number of elements in a collection. If there are no collections, there no elements, hence its length should be zero.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since length(zip(itrs...)) == minimum(length, itrs) == mapreduce(length, min, itrs), the value length(zip()) should be the identity element of min. The identity element of min on bounded set is the maximum of the set (it has to "loose" against everything else when taking min). Since the output of length can take any non-negative integer, "infinite" is a reasonable answer.

Another way to think about is that, if you think the mapping (a, b) -> zip(a, b) as a monoid and define zip(itrs...) = reduce(zip, itrs) [*1], the iterator zip() should be the identity of the monoid. An infinite stream of empty tuples is the identity element of the zip monoid. The length of an infinite stream is, well, infinite.

[*1] You have to "post-process" to flatten the output tuple. But other than that, this monoid argument is accurate.

Copy link
Member

@tkf tkf Jun 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yet another way to convince yourself is to consider a function

function zip′(itrs...)
    a, b = anysplit(itrs)
    return ((x..., y...) for (x, y) in zip(zip(a...), zip(b...)))
end

with a function anysplit that splits a tuple at some location such that

ys, zs = anysplit(xs::Tuple)
@assert (ys..., zs...) === xs

It is desirable for the function zip′ to behave like zip for any anysplit function.

In particular, for this to hold for anysplit(xs) = (), xs, we need zip() to produce an infinite stream of empty tuples.

(Edit: fix a typo in anysplit signature)

Copy link
Contributor

@jonas-schulze jonas-schulze Jun 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation! The monoid view was helpful. I agree that the stream of empty tuples would be the neutral element, if we consider equivalence under "generates the same elements". Though it's not intuitive to me why zip() would be repeated(()).

I know that the following is currently wrong in Julia, but as we already consider the empty tuple to be identical to "no element", I would expect this:

using Test

@testset "empty collections" begin
    null = Vector{Union{}}()
    @test collect(zip(())) == null
    @test collect(zip()) == null # fails
    @test collect(()) == null
    @test collect() == null # fails
end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the other hand, having a zip name for the zip-neutral element kinda makes sense.

Copy link
Member

@tkf tkf Jun 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's as counter-intuitive as the neutral element of min being the maximum. Or maybe more like the intersection of empty set being the universe. I agree it feels like a paradox but "fixing it" would break mathematical coherence behind the API.

Copy link
Member

@tkf tkf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM other than the minor nitpicks I mentioned.

test/iterators.jl Outdated Show resolved Hide resolved
test/iterators.jl Outdated Show resolved Hide resolved
test/iterators.jl Outdated Show resolved Hide resolved
@kshyatt kshyatt added the test This change adds or pertains to unit tests label Jun 30, 2020
Minor fixes.

Co-authored-by: Takafumi Arakaki <[email protected]>
@ravibitsgoa
Copy link
Contributor Author

Hi everyone,
Can someone review this PR and merge?

@KristofferC KristofferC added the status:merge me PR is reviewed. Merge when all tests are passing label May 6, 2022
@KristofferC KristofferC merged commit a593f31 into JuliaLang:master May 9, 2022
@giordano giordano removed the status:merge me PR is reviewed. Merge when all tests are passing label May 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
test This change adds or pertains to unit tests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants