Consider the following program, which utilizes an if
statement:
numbers = Array.new
if numbers.length < 10
new_number = rand(100)
numbers.push(new_number)
end
len = numbers.length
p numbers
p len
Click here for a REPL to try it.
What do you expect the output of the program to be when you click run? Try to interpret the program yourself before you ask Ruby to.
Okay, now you can click “run ▶”. Did you guess right?
We start off with a blank array, numbers
. If its length is less than 10
(this is true, since length is currently 0
), we push a new random number into it.
Once Ruby reaches the if
’s end
, it proceeds to the next line and continues to execute the rest of the code (whether the if
’s condition was true or not).
At the end of the day, numbers
has one element in it and len
is 1
.
Now, consider almost identical code, but with the if
keyword swapped for a new keyword — while
:
numbers = Array.new
while numbers.length < 10
new_number = rand(100)
numbers.push(new_number)
end
len = numbers.length
p numbers
p len
Click here for a REPL to try it.
while
works almost exactly like if
— it evaluates the expression next to it, and if the expression is truthy, it executes the code on the lines between it and it’s end
; if not, it ignores the code on the lines between it and it’s end
.
There is one key difference: if the condition next to a while
is truthy, after we reach it’s end
, the execution of the program jumps back up to the while
statement.
Then the condition is evaluated again. If it is still true, then the code inside the while
statement is executed again. And then the execution of the program jumps back up to the while
statement again. Etc.
So in this case,
end
, we jump back up to the while
.a.length < 10
again — still true, since 1 < 10
.2 < 10
? Yep, so we do it again.3 < 10
? Yep, so we do it again.4 < 10
? Yep, so we do it again.10 < 10
? Nope, so now proceed to the line after the end
and continue.len
ends up being 10
.What we’ve seen here is our very first loop; code that is executed multiple times. It could be an arbitrary number of times, perhaps even an infinite number of times if we aren’t careful.
Fundamentally, all looping is implemented with while
; but, this being Ruby, there are all sorts of convenience methods on top to make it as easy as possible to create loops for various contexts. For example, let’s say I wanted to print:
"1 Mississippi"
"2 Mississippi"
"3 Mississippi"
# etc
"10 Mississippi"
exactly 10 times. I could do it using while
like this; try interpreting the following code before you click “run”:
mississipis = 1
while mississipis <= 10
p mississipis.to_s + " Mississippi"
mississipis = mississipis + 1
end
Click here for a REPL to try it.
Does the code make sense to you?1
Or, rather than while
, I could use Integer
’s .times
method, like this:
mississipis = 1
10.times do
p mississipis.to_s + " Mississippi"
mississipis = mississipis + 1
end
Click here for a REPL to try it.
Notice there’s a new keyword here: do
. This is because the .times
method, in order to do its job of executing some code 10 times, needs a special argument — the code to execute.
In order to pass a method some lines of code as an argument, we need to wrap the lines of code within the do
and end
keywords, creating what’s called a block of code.
So, given a block of code, the 10.times
method will execute it for us exactly 10 times; this saves us the trouble of writing a condition for while
.
But the .times
method will save us even more trouble than that; we can stop worrying about creating and incrementing the counter variable, mississipis
, too. The .times
method will create a block variable and assign values to it for us automatically, but we have to choose a name for it using some new syntax after the do
: the vertical bars, | |
, or “pipes”. It looks like this:
10.times do |mississipis|
p mississipis.to_s + " Mississippi"
end
Click here for a REPL to try it.
Try running it. Here’s what’s going on:
do
/end
and gave it to .times
.mississipis
, with the | |
after the do
..times
method did mississipis = 0
before the first iteration..times
method executed the block of code the first time..times
method did mississipis = 1
before the second iteration..times
method executed the block of code the second time.Why does .times
start by assigning 0
to its block variable during the first iteration, rather than 1
? Well, that’s just how the author of the .times
method made it work.
Fortunately, Ruby provides lots of other looping convenience methods that we can take advantage of instead, and each one assigns different values to its block variable.
In the REPL above, replace 10.times
with each of the following and play around with the arguments to get a sense of how each method works:
5.upto(10)
99.downto(90)
1.step(10, 3)
10.step(1, -4)
If the line mississipis = missippis + 1
looks a little odd to you, you’re not alone. Remember, this is variable assignment, not equivalence. So the expression on the right side (mississipis + 1
) is evaluated first until there’s just one object (e.g 2
) left; and then that object replaces the contents of the variable (missippis
) named on the left. Rinse and repeat. ↩