This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
immibis's profile picture

Loops

Started by immibis, 02 January 2013 - 04:22 PM
immibis #1
Posted 02 January 2013 - 05:22 PM
(Because I see too many people using code like:

-- Code in italics is wrong
function run()
  -- do something
  run()
end
run()

and wondering why they get a stack overflow. This is not a good way to make something run more than once. Nor is os.reboot)

Sometimes you want to make a section of code run more than once. Lua provides several constructs, known as loops.

Note:
In this tutorial, text in <angle brackets=""> indicates where you should write something, eg: print(<thing to="" print="">)

Note:
Within a function, code runs from top to bottom unless otherwise specified.
Most of you should know this by now, but it's not obvious to everyone and I have seen code written by people who didn't know it.
In particular, this means code after the loop will not run until the loop has finished.

Note:
A condition can be any value - you could write something like "while 7 do". For the purposes of loops and if statements, Lua considers nil to be equivalent to false, and any other value to be equivalent to true.

While loops
A while loop runs a block of code over and over until some condition is false.
This is the most fundamental type of loop in Lua, and can be used to simulate all other loops.
First it checks the condition, then if the condition is true it runs the code, then it checks the condition, runs the code if it's true, etc… When it checks the condition, and it's false, it goes on to the code after the loop.

The syntax for a while loop in Lua is:

while <condition> do
  <code>
end
and does this:
1. Check the condition.
2. If the condition is true:
2A. Run the code.
2B. Go to step 1.

Note: Each time the computer "goes around the loop" is called an iteration.

Example:

print("Do you want me to spam you?")
while read() == "yes" do
  print("spam")
  print("spam")
  print("spam")
  print("spam")
  print("spam")
  print("Do you want me to do it again?")
end
print("OK, bye then")

This will do the following:
1. Print "Do you want me to spam you?"
2. Wait for the user to enter some text.
3. If that text is "yes" then:
3A. Print "spam" five times
3B. Print "Do you want me to do it again?"
3C. Go to step 2.
4. Print "OK, bye then"

Notice that the condition is only checked at the start of each iteration.
If we have something like this:

A = 7
while A == 7 do
  A = 8
  print("Hi!")
end
then it will print "Hi!" once, even though A has stopped being 7 when the computer gets to the print("Hi!")

Repeat/until loops
Repeat/until loops are a variation on while loops, where the condition is not checked until the end of each iteration, and the loop runs as long as the condition is false (instead of true).

The computer runs the code, then checks the condition, then runs the code, then checks the condition, etc… until the condition is true when the computer checks it.

"Do/while loops" are a similar variation found in some other languages. Lua does not have do/while loops so they will not be covered here.

The syntax in Lua is:

repeat
  <code>
until <condition>
which does this:
1. Run the code.
2. Check the condition.
3. If the condition is false, go to step 1.

Example:

repeat
  print("spam")
  print("spam")
  print("spam")
  print("spam")
  print("spam")
  print("Do you want me to keep spamming you?")
until read() == "no"
print("OK, bye then")

This is similar to the last example, but not quite the same. For one thing, it will only stop when the user enters "no", instead of anything except "yes". For another, it will always spam at least once.

It does exactly this:
1. Print "spam" five times.
2. Print "Do you want me to keep spamming you?"
3. Wait for the user to enter a line of text.
4. If they didn't enter "no", go to step 1.
5. Print "OK, bye then"

Numeric for loops
A numeric for loop increments a variable on each iteration. You can use a while or repeat/until loop for this purpose, but it's used so commonly that it has its own type of loop.

In other languages, these are often just called "for loops".

In Lua, the syntax is either of these:

for <variable> = <start>, <stop> do
  <code>
end

for <variable> = <start>, <stop>, <step> do
  <code>
end

This is easier to explain by example, so here is a very simple use of a numeric for loop:

for number = 1, 5 do
  print("Hello world! ", number)
end
This will print:

Hello world! 1
Hello world! 2
Hello world! 3
Hello world! 4
Hello world! 5

As you can see, this ran five iterations.
On the first iteration, number was equal to 1.
On the second iteration, number was equal to 2, and so on.

<variable> (in this case, number) is just the name for the variable that changes automatically. You can use any valid variable name. Traditionally, if there's no obvious name for this variable it will be called i, j or k. This variable is also known as the loop counter.
<start> (in this case, 1) is the value the variable has on the first iteration.
<stop> (in this case, 5) is the value the variable has on the last iteration.

We haven't covered <step> yet. <step> indicates how much the variable should change by on each iteration, and if you don't specify it, it will be 1, as in the example above. Let's change the previous example by adding 2 as <step>:

for number = 1, 5, 2 do
  print("Hello world! ", number)
end
This will print:

Hello world! 1
Hello world! 3
Hello world! 5
As you can see, the loop counter still starts at 1 and ends at 5, but it increases by 2 on each iteration.
You can also use a negative <step>:

for number = 5, 1, -1 do
  print(number)
end
print("Blast-off!")
prints:

5
4
3
2
1
Blast-off!

Numeric for loops are also useful for repeating code a certain number of times:

for k = 1, 5 do
  print("spam")
end
prints:

spam
spam
spam
spam
spam

Generic for loop
This is the most powerful loop in Lua. Its main use is with tables, so if you haven't learned about tables yet, skip this section and come back later.

The syntax is:

for <variable list=""> in <iterator expression=""> do
  <code>
end

<iterator expression=""> sounds scary, and it is, if you look at the details. But you don't need to know that, all you need to know is what things you can put there.
<variable list=""> is a comma-separated list of variables to use in the loop, and it depends on <iterator expression="">.

The most common use is with the built-in pairs or ipairs function and tables:

for <key variable="">, <value variable=""> in pairs() do
  <code>
end

Example:

t = {
  "hello world!",
  one = 1,
  five = 5,
  seven = 7,
}
for key, value in pairs(t) do
  print(key," is ",value)
end
prints: (order of lines may vary)

1 is "hello world!"
five is 5
seven is 7
one is 1

It picks an entry from the table, sets the key and value variables to the key and value, and then runs the code. Then it picks a different entry, etc, until it's processed all the entries. There is no guarantee about which order it will choose the entries in.

Note: Do not add new entries to the table inside the loop, or you'll get undefined behaviour (which means literally anything can happen; it might work, it might not, it might raise an error). You are allowed to delete entries or modify existing ones.

ipairs is similar, but it only chooses numeric keys starting from 1 and until the first gap, and it is guaranteed to return the entries in the right order.


t = {
  "line 1",
  "line 2",
  "line 3",
  [6] = "line 6",
  [4] = "line 4",
  five = "line 5"
}
for key, value in ipairs(t) do
  print(key," is ",value)
end
prints:

line 1
line 2
line 3
line 4
Notice that the lines were printed in the order they appeared in the table. "line 5" was not printed as it was not associated with a numeric key, and "line 6" was not printed as it appears after the first gap. (since there was no table entry with 5 as the key)



For advanced coders:
A useful effect of the generic for loop is that it can be used to call a function repeatedly until it returns nil. This code will print all the lines in the file "hello":

f = fs.open("hello", "r")
for line in f.readLine do -- note: no () after readLine
  print(line)
end
f.close()
</code>

</value></key></iterator></variable></iterator></code></iterator></variable></step></step></step></step></stop></start></variable></code></step></stop></start></variable></code></stop></start></variable></condition></code></code></condition></thing></angle>
Doyle3694 #2
Posted 02 January 2013 - 10:34 PM
A really good read! Didn't even know that last code snippet would work, but guess you learn new stuff everyday, right? ;)/> It is very easy to follow along, I will surely point people having problems with loops in this direction!
InputUsername #3
Posted 02 January 2013 - 11:40 PM
This is a superb tutorial! I learned a lot even though I thought I new a fair bit about loops. Great job, I wish every tutorial was written like this. Anyway, keep up the good work!
Leo Verto #4
Posted 03 January 2013 - 03:27 AM
Very good tutorial immibis, although "A while loop runs a block of code over and over until some condition is false." is not quite exact, a while loop runs the code while the condition is true. ;)/>
immibis #5
Posted 03 January 2013 - 03:11 PM
Very good tutorial immibis, although "A while loop runs a block of code over and over until some condition is false." is not quite exact, a while loop runs the code while the condition is true. ;)/>/>
The condition is either true or false, so what's the difference?
Leo Verto #6
Posted 04 January 2013 - 04:17 AM
Very good tutorial immibis, although "A while loop runs a block of code over and over until some condition is false." is not quite exact, a while loop runs the code while the condition is true. ;)/>/>
The condition is either true or false, so what's the difference?
A while runs the the code while the condition is true, a repeat/until repeats it until the condition is true, sorry if I didn't say that clearly.
It's a small mistake and certainly not very important but it's the runtime condition and not the stop condition.
Dlcruz129 #7
Posted 04 January 2013 - 06:14 AM
Very good tutorial immibis, although "A while loop runs a block of code over and over until some condition is false." is not quite exact, a while loop runs the code while the condition is true. ;)/>/>
The condition is either true or false, so what's the difference?

Couldn't it be nil? Anyways, great tutorial.
shiphorns #8
Posted 30 January 2013 - 07:38 PM
Excellent. I would recommend adding mention of 'break' since users will encounter this a lot, especially in combination with loops of the format "while true do <condition that might break> end"
shiphorns #9
Posted 02 February 2013 - 04:33 PM
(Because I see too many people using code like:

-- Code in italics is wrong
function run()
  -- do something
  run()
end
run()

and wondering why they get a stack overflow. This is not a good way to make something run more than once. Nor is os.reboot)

Actually, this construct shouldn't overflow the stack if Lua is implemented correctly. If this is crashing CC, it's really a CC implementation bug, not a coding mistake on the part of the Lua coder. It's part of the Lua spec that proper tail calls be supported, even when recursive (function calling itself from its last line). Each time run() is called from the end of run(), the previous run() is supposed to be taken off the stack because run() is the very last statement of run() and there is no need to return to it. Tail recursion can always be converted to a loop, and doing so is usually an optimization because in most languages looping has less overhead than function calls . In languages without proper tail call, it's considered correct to do that optimization to avoid growing the stack. In Lua, it's optional–more of a style thing–assuming the VM handles it as it should.

Lua need not support the following, however, because it's not a tail call. In this example, each run() has to stay on the stack in order for all the 'return true' statements to execute when it unwinds:

function run()
  -- do something
  run()
  return true
end
run()
Shnupbups #10
Posted 02 February 2013 - 09:19 PM
Good tutorial! I, when I was a little n00b, did not know how for loops worked, and I couldn't get off my lazy arse to figure it out. So, to all the n00bish lazy arses out there, read this topic!
pipa #11
Posted 04 February 2013 - 05:25 AM
Should ve read this before the wiki, this is much more better to understand. Nice work.
fodakahn #12
Posted 04 February 2013 - 12:17 PM
This was an awesome tutorial, but I'm still having trouble getting my specific task done with CCraft lua…

I learned a lot though, thanks!
immibis #13
Posted 07 February 2013 - 10:40 AM
Actually, this construct shouldn't overflow the stack if Lua is implemented correctly. If this is crashing CC, it's really a CC implementation bug, not a coding mistake on the part of the Lua coder. It's part of the Lua spec that proper tail calls be supported, even when recursive (function calling itself from its last line). Each time run() is called from the end of run(), the previous run() is supposed to be taken off the stack because run() is the very last statement of run() and there is no need to return to it. Tail recursion can always be converted to a loop, and doing so is usually an optimization because in most languages looping has less overhead than function calls . In languages without proper tail call, it's considered correct to do that optimization to avoid growing the stack. In Lua, it's optional–more of a style thing–assuming the VM handles it as it should. Lua need not support the following, however, because it's not a tail call. In this example, each run() has to stay on the stack in order for all the 'return true' statements to execute when it unwinds:
 function run() -- do something run() return true end run() 

From the Lua specification:
A call of the form return functioncall is called a tail call. … So, none of the following examples are tail calls:

	 return (f(x))		-- results adjusted to 1
	 return 2 * f(x)
	 return x, f(x)	   -- additional results
	 f(x); return		 -- results discarded
	 return x or f(x)	 -- results adjusted to 1


function run()
   ...
   run()
end
is not considered tail recursion by Lua because it discards the return values. You would have to change the last line to "return run()", but this is still not a good alternative to a while loop, because do you really expect new programmers to understand tail recursion?
Demikaa #14
Posted 07 February 2013 - 11:59 PM
Thank you, very helpful to me :)/>
shiphorns #15
Posted 08 February 2013 - 08:59 AM
is not considered tail recursion by Lua because it discards the return values. You would have to change the last line to "return run()", but this is still not a good alternative to a while loop, because do you really expect new programmers to understand tail recursion?

Yes, sorry that's what I meant my example to have: return run(), that was sloppy on my part, and what I get for copy-pasting your example and not looking at it more carefully. I was seeing what I wanted to see, not what was there…

I don't necessarily expect new programmers to understand tail recursion (though it's arguably the least confusing variant of recursion :-), I just didn't want them to get the impression that tail recursion is always bad in all cases, because they're going to come across it used deliberately by Lua coders who know what they are doing, and that could be confusing (i.e. hey I thought this was bad to do?). It's definitely better to have this discussion, so that the new coders know when it can cause problems, and why.

In most other programming languages, tail recursion should be converted to a loop, because it would grow the stack and has the overhead of a function call for each iteration. Some C/C++ compilers can even recognize tail recursion and compile it to a loop for you (if this optimization is enabled). But I'm sure there are people who are Lua experts who understand that it's handled properly in Lua and know how to use it correctly, and new coders will eventually see examples of this.

I also agree wholeheartedly, Lua or no, that the example you give of using recursion to keep a program running is bad form. Absolutely that sort of thing should be a while loop.
scubadiver1991 #16
Posted 30 March 2013 - 05:51 PM
I just wanted to say thank you. The lack of labels in CC had me scrambling to learn how to better utilize functions and loops, and I just keep coming back to your little summary of loops here. As for the post above me, I'm one of those new coders and the whole idea of tail recursion is a little above my head at the moment. I'm still swallowing the idea of the stack.
TheOddByte #17
Posted 01 April 2013 - 12:55 AM
Really a great tutorial! :D/>
Even though I thought I knew everything about loops I just learned a little more.