Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In a for loop, when are the loop parameters evaluated?

Here's a real beginner question...

In a for loop, when are the loop parameters evaluated?

Here, the loop runs forever, so c is obviously 'being checked' each time the loop starts:

c= [1]
for i in c
   push!(c, i)
   @show c
end
c = [1,1]
c = [1,1,1]
c = [1,1,1,1]
...

But this loop is only evaluated once:

c= [1]
for i in 1:length(c)
   push!(c, i)
   @show c
end
c = [1,1]

This one looks like it evaluates enumerate(c) every loop:

c= [1]
for (i, _) in enumerate(c)
    push!(c, i)
    @show c
end
c = [1,1]
c = [1,1,1]
c = [1,1,1,1]
...

But this loop obviously doesn't:

c= [1]
for i in eachindex(c)
    push!(c, i)
    @show c
end
c = [1,1]

And this does:

c= [1]
foreach(a -> (push!(c, a); @show c), c)
c = [1,1]
c = [1,1,1]
c = [1,1,1,1]
...    

As I said, it's a real beginner question. But am I missing a general pattern?

like image 315
daycaster Avatar asked Dec 11 '16 09:12

daycaster


1 Answers

I believe the main point is that your various loops are invoking Julia's iterator interface on two different types of object:

  • the array object c itself

  • an AbstractUnitRange object (or one of its subtypes)


When you loop with for i in c, Julia doesn't know how large c is. All Julia needs is the current state of iteration (the index it has reached) and what the next index to visit in c should be. It can test if it is finished iterating over c if this next index will take it out of bounds.

Copying from Julia's iterator interface documentation, such a loop essentially boils down to:

state = start(c)
while !done(c, state)
    (i, state) = next(c, state)
    # body
end

If you append to c in the body of the loop, there will always be a next index to visit (i.e. while !done(c, state) will always be true). The array c can grow until your memory is full.

The loops using enumerate and foreach both depend on the iterator interface to the array c in the same way, and so you see similar behaviour when modifying c during these loops.


On the other hand, the loops using for i in 1:length(c) and for i in eachindex(c) do not iterate over c itself, but different objects supporting the iterator interface.

The key point is that these objects are created before iteration begins and they are not affected when modifying c in the body of the loop.

In the first case, length(c) is computed and then 1:length(c) is created as an object of UnitRange type. In your example it starts at 1 and stops at 1, so we push! to c only once during iteration.

In the second case, calling eachindex([1]) returns a Base.OneTo object; just like a UnitRange object, except guaranteed to start at 1. In your example case Base.OneTo(1) is created and start at 1 and stops at 1 too.

Both of these objects are subtypes of AbstractUnitRange and ultimately subtypes of AbstractArray. The iterator interface allows you to access the values held by these objects in sequence.

like image 127
Alex Riley Avatar answered Sep 20 '22 20:09

Alex Riley