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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With