import Random.seed! as seed!
import Random.shuffle as shuffle
seed!(1); # [Code I]
for i = shuffle(1:3)
for j = shuffle(1:4)
println("($i, $j)")
end
end
seed!(1); # [Code II]
for i = shuffle(1:3), j = shuffle(1:4)
println("($i, $j)") # its inner layer sequence varies!
end
seed!(1); # [Code III]
let I = shuffle(1:3), J = shuffle(1:4)
@show I J;
for i = I, j = J
println("($i, $j)") # its inner layer sequence are the same.
end
end;
In my intuition (and I believe it should be so), Code IIshould resolves intoCode III. (cf. my intuition here).
But the current behavior implies as if Code II instead resolves into Code I, which I think is not understandable.
The style in Code I and Code II are very different—e.g., only one break is entailed to escape the for in Code II, whereas you need two in Code I.
In the first two examples, you call shuffle(1:4) creating a new iterator in each iteration of the i loop. In the last example you call shuffle(1:4) once and reuse same iterator. It’s as if
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
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.
I’m not sure what you mean by resolve, but all 3 versions certainly parse differently, and the multiple blocks in Code II is only practically equivalent to the single block in Code I because only the innermost for-block has code for iterations which lacks a break. If you need to write code in between for blocks or need single-level breaks, then Code II is not usable. The merged for-loop syntax of Code II is really only useful for that all-level break when you can’t easily combine all of the possibly interdependent loop iterables into one big one.