Cartesian For Loop

Well, it’s possible to write an iterator which is updated. A stateful iterator.

julia> iter1 = 1:3
1:3

julia> iter2 = Iterators.Stateful(1:3)
Base.Iterators.Stateful{UnitRange{Int64}, Union{Nothing, Tuple{Int64, Int64}}}(1:3, (1, 1))

julia> for i in iter1
           println(i)
       end
1
2
3
# and again:
julia> for i in iter1
           println(i)
       end
1
2
3

# then the stateful iter2:
julia> for i in iter2
           println(i)
       end
1
2
3
# but now it's empty:
julia> for i in iter2
           println(i)
       end

julia> 

Semantically they make local variables, but it’s just not the same syntax, and we do different things with them. local doesn’t necessarily have an assignment, but when it does, it works a lot like plain assignments, including destructuring:

julia> let
       local x, y = 1:2
        x, y
       end
(1, 2)

julia> let
       local (;x, y) = (y=2, x=1)
        x, y
       end
(1, 2)

let headers don’t do that

julia> let x, y = 1:2
        x, y
       end
ERROR: UndefVarError: `x` not defined in local scope

unless you do something like argument destructuring in method definitions, which is what let blocks are mimicking.

julia> let (x, y) = 1:2
        x, y
       end
(1, 2)

It could potentially have been more consistent, but that would also disallow many conveniences in plain assignments.

I think it’s fine to modify an existing element, the problem is insertion/deletion changing the indices or set of keys.

1 Like

It’s actually not written in the docs (I think) that the shuffle(1:9) is only evaluated once. Or, it is only inferred, since what comes after = should be an iterator, and shuffle(1:9) is a call, not an iterator. This is different from e.g. a while loop, where the condition is evaluated every time.

When in doubt, it is possible to look at what happens lower down, e.g. with:

julia> f(n) = for i in g(n); println(i); end
f (generic function with 1 method)

julia> @code_typed f(10)
CodeInfo(
1 ─ %1  = Main.g::Any
│   %2  = (%1)(n)::Any
│   %3  = Base.iterate(%2)::Any
│   %4  = (%3 === nothing)::Bool
│   %5  = Base.not_int(%4)::Bool
└──       goto #4 if not %5
2 ┄ %7  = φ (#1 => %3, #3 => %11)::Any
│   %8  = Core.getfield(%7, 1)::Any
│   %9  = Core.getfield(%7, 2)::Any
│         Main.println(%8)::Any
│   %11 = Base.iterate(%2, %9)::Any
│   %12 = (%11 === nothing)::Bool
│   %13 = Base.not_int(%12)::Bool
└──       goto #4 if not %13
3 ─       goto #2
4 ┄       return nothing
) => Nothing

Recalling that for loops are rewritten as while loops as described in Interfaces · The Julia Language, it’s possible to see that g is called only once (%2 = (%1)(n)), in section #1 before the loop starts in section #2.

(The φ-node at the start of section #2 (%7 = φ ...), means that variable %7 is set to %3 if section #2 was reached from section #1, but to %11 if it was reached from section #3. This weird intermediate format is SSA (Static Single Assignment), easy for the compiler, not for normal people)

1 Like

Well, the result of the shuffle call, i.e. the Vector{Int} shuffle(1:9) is an iterator, as it supports iterate :slight_smile: