Generator sometimes works, sometimes not

It’s about Generator Expressions.
I used to only know about sum(i for i in 1:I).
But today I happened to discover that we can also write minimum(i for i in 1:I).
I’m thus intrigued:

  • Do all function who takes an Vector arg can comprehend grammar like this?

I then came up with a counter-example which fails

julia> a
4-element Vector{Bool}:
 0
 0
 1
 1

julia> b
4-element Vector{Bool}:
 0
 1
 0
 1

julia> findall(a .& b)
1-element Vector{Int64}:
 4

julia> [(i & j) for (i, j) in zip(a, b)]
4-element Vector{Bool}:
 0
 0
 0
 1

julia> findall( (i & j) for (i, j) in zip(a, b) )
ERROR: MethodError: no method matching keys(::Base.Iterators.Zip{Tuple{Vector{Bool}, Vector{Bool}}})
The function `keys` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  keys(::Cmd)
   @ Base process.jl:716
  keys(::Base.TermInfo)
   @ Base terminfo.jl:232
  keys(::BenchmarkTools.BenchmarkGroup)
   @ BenchmarkTools K:\judepot1114\packages\BenchmarkTools\1i1mY\src\groups.jl:75
  ...

Stacktrace:
 [1] keys(g::Base.Generator{Base.Iterators.Zip{Tuple{Vector{Bool}, Vector{Bool}}}, var"#331#332"})
   @ Base .\generator.jl:55
 [2] pairs(collection::Base.Generator{Base.Iterators.Zip{Tuple{Vector{Bool}, Vector{Bool}}}, var"#331#332"})
   @ Base .\abstractdict.jl:178
 [3] findall(A::Base.Generator{Base.Iterators.Zip{Tuple{Vector{Bool}, Vector{Bool}}}, var"#331#332"})
   @ Base .\array.jl:2691
 [4] top-level scope
   @ REPL[393]:1

It’s unrelated to syntax and parsing. findall is documented to only work on things with indices or keys that work with the methods keys and pairs. Zip-ped generators currently don’t, but if someone implements keys method, it could. On the other hand, minimum is documented to work with iterators (by implication of its argument name itr, so not very formally), which all generators should be.

Just in case, bear in mind that you can get away with omitting the parentheses for generator expressions in function calls when it’s the only argument before ;. If there are other arguments, proper syntax demands those parentheses, and the consequences are unfortunately silent:

julia> sum(i for i in 1:10)
55

julia> sum(i for i in 1:10, init=100) # parsed as 2-level nested loop
55

julia> sum((i for i in 1:10), init=100) # parsed as 2 arguments
155

julia> sum(i for i in 1:10; init=100) # ; not in loops, so parsed as 2 arguments
155
5 Likes

Generators are unrelated to Vectors or function calls, they’re just regular ol’ objects:

julia> (i for i in 1:3)
Base.Generator{UnitRange{Int64}, typeof(identity)}(identity, 1:3)

There’s some special support in the parser for constructing them, but that’s just syntax sugar, you can use Iterators instead. Especially Iterators.map and Iterators.filter:

julia> (i for i in 1:3) === Iterators.map(identity, 1:3)
true

Furthermore, any user may implement their own iterator types easily:

2 Likes

Thank you, learned a lot🙂

2 Likes