Cartesian For Loop

It really is, maybe we just need a louder example:

julia> struct PrintRange{R<:AbstractRange} r::R end

julia> PrintRange(r::R) where R<:AbstractRange = (println("hello, new range $(r)!"); PrintRange{R}(r))
PrintRange

julia> Base.iterate(pr::PrintRange, args...) = iterate(pr.r, args...)

julia> for i in PrintRange(1:2), j in PrintRange(3:4)
         println(i, j)
       end
hello, new range 1:2!
hello, new range 3:4!
13
14
hello, new range 3:4!
23
24

This is documented under Control Flow:

Multiple nested for loops can be combined into a single outer loop, forming the >cartesian product of its iterables:

julia> for i = 1:2, j = 3:4
           println((i, j))
       end
(1, 3)
(1, 4)
(2, 3)
(2, 4)

With this syntax, iterables may still refer to outer loop variables; e.g. for i = 1:n, j = 1:i is valid. However a break statement inside such a loop exits the entire nest of loops, not just the inner one.

PrintRange(3:4) executed for each i value (Code I behavior) because it could have depended on it, not together with PrintRange(1:2) in the header prior to all iterations as you expected (Code III behavior). Code I+II and Code III are semantically different, so 1) the results can differ if the inner loop’s call does not only depend on its arguments, as you demonstrated with a PRNG, and 2) if the call is expensive, Code III saves time at no cost. Common subexpression elimination may optimize Code I+II to an equivalent of Code III if the compiler recognizes the inner call is pure, but that’s not as reliable as us writing what we mean, and sometimes we really would like side effects like sleep.

There’s nothing that can be done about the break exiting all the loops of the same block; if it only exited the inner one, then there’s no way to exit any of the other loops. break-ing out of loops separately is already an option with multiple nested blocks. continue does work across the inner loop either way because it skips to the next iteration.

3 Likes