3

Ok here's a basic for loop

local a = {"first","second","third","fourth"}
for i=1,#a do 
    print(i.."th iteration")
    a = {"first"}
end 

As it is now, the loop executes all 4 iterations.

Shouldn't the for-loop-limit be calculated on the go? If it is calculated dynamically, #a would be 1 at the end of the first iteration and the for loop would break....

Surely that would make more sense? Or is there any particular reason as to why that is not the case?

SatheeshJM
  • 3,575
  • 8
  • 37
  • 60

3 Answers3

7

The main reason why numerical for loops limits are computed only once is most certainly for performance.

With the current behavior, you can place arbitrary complex expressions in for loops limits without a performance penalty, including function calls. For example:

local prod = 1
for i = computeStartLoop(), computeEndLoop(), computeStep() do
  prod = prod * i
end

The above code would be really slow if computeEndLoop and computeStep required to be called at each iteration.

If the standard Lua interpreter and most notably LuaJIT are so fast compared to other scripting languages, it is because a number of Lua features have been designed with performance in mind.


In the rare cases where the single evaluation behavior is undesirable, it is easy to replace the for loop with a generic loop using while end or repeat until.

local prod = 1
local i = computeStartLoop()
while i <= computeEndLoop() do
  prod = prod * i
  i = i + computeStep()
end
prapin
  • 6,395
  • 5
  • 26
  • 44
1

The length is computed once, at the time the for loop is initialized. It is not re-computed each time through the loop - a for loop is for iterating from a starting value to an ending value. If you want the 'loop' to terminate early if the array is re-assigned to, you could write your own looping code:

local a = {"first", "second", "third", "fourth"}                                                                           

function process_array (fn)                                                                                                
   local inner_fn                                                                                                          
   inner_fn =                                                                                                              
      function (ii)                                                                                                        
         if ii <= #a then                                                                                                  
            fn(ii,a)                                                                                                       
            inner_fn(1 + ii)                                                                                               
         end                                                                                                               
      end                                                                                                                  
   inner_fn(1, a)                                                                                                          
end                                                                                                                        


process_array(function (ii)                                                                                                
                    print(ii.."th iteration: "..a[ii])                                                                     
                    a = {"first"}                                                                                          
                     end) 
Kyle Burton
  • 26,788
  • 9
  • 50
  • 60
0

Performance is a good answer but I think it also makes the code easier to understand and less error-prone. Also, that way you can (almost) be sure that a for loop always terminates.

Think about what would happen if you wrote that instead:

local a = {"first","second","third","fourth"}
for i=1,#a do 
    print(i.."th iteration")
    if i > 1 then a = {"first"} end
end

How do you understand for i=1,#a? Is it an equality comparison (stop when i==#a) or an inequality comparison (stop when i>=#a). What would be the result in each case?

You should see the Lua for loop as iteration over a sequence, like the Python idiom using (x)range:

a = ["first", "second", "third", "fourth"]
for i in range(1,len(a)+1):
    print(str(i) + "th iteration")
    a = ["first"]

If you want to evaluate the condition every time you just use while:

local a = {"first","second","third","fourth"}
local i = 1
while i <= #a do 
    print(i.."th iteration")
    a = {"first"}
    i = i + 1
end
catwell
  • 6,770
  • 1
  • 23
  • 21