In Progress
Unit 1, Lesson 1
In Progress

Loop

Video transcript & code

Most of the time when we write a loop in a program, we have a specific end state in mind. For instance, we want to iterate all of the elements of a collection. However, there are exceptions to this rule.

Consider this small program. It implements a trivial server which simply echoes messages back, reversed. If we start it up in one shell and then telnet into the appropriate port from another, we can see that it works well.

require 'socket'

server = TCPServer.new 9001

while true do
  client = server.accept
  input = client.readline.chomp
  client.puts input.reverse
  client.close
end

In this code we can see a a pattern that's very common at the top-level of applications: a main-loop. Unlike most loops, main loops are usually intended to loop forever, or at least until interrupted by some signal.

In order to loop forever, this program uses a construct that I often run across in Ruby programs. It uses a while loop with a condition of true.

This functions perfectly well as an infinite loop. And in many programming languages, this or something very like it is the idiomatic way to write an infinite loop.

But Ruby is not other programming languages. Ruby puts a great deal of stock in code that expresses the intent of the programmer. So while this works, and while it is the solution that many programmers naturally think of for a main loop, it is not the idiomatic way to write an infinite loop.

Instead, if we want some code to loop forever in Ruby, we should use the Kernel method that exists precisely for that purpose, called #loop.

require 'socket'

server = TCPServer.new 9001

loop do
  client = server.accept
  input = client.readline.chomp
  client.puts input.reverse
  client.close
end

This is one of those little changes that makes a difference, at least to me. When I see a while loop, a part of my brain immediately starts thinking, "OK, this is a loop, let's see what the condition is…". Then I read the true part, and think: "oh, it's a loop forever".

Whereas when I see loop invoked, I instantly think "ah, an infinite loop", and move on to read the loop body without any wasted cycles.

Whether or not you already use #loop, you might wonder if it differs in any ways from a while true loop. As far as I know, there are two differences.

First off: like all other standard Ruby methods that deal with iteration, when invoked without a block #loop returns an Enumerator. The Enumerator that is returned is a kind of "infinite bucket of nothing". It can be advanced forever, and will always return nil. It's a bit like reading from the /dev/zero device in Linux.

enum = loop                     # => #<Enumerator: main:loop>
enum.next                       # => nil
enum.next                       # => nil
enum.next                       # => nil

I was tempted to show some an example of how we could use this infinite nil enumerator to perform some abstract programming tricks, but frankly the examples I could come up with were a bit contrived. If you have an idea of a practical way to use this enumerator, please send it in.

The other difference between loop and while true is slightly more relevant. Like any block, the block passed to loop has its own variable scope. Whereas a while loop doesn't introduce any new scopes.

To understand what this means, lets construct a while loop which assigns a variable internally, and then immediately breaks out. When we examine the variable, we are able to read the value that was set inside the loop.

while true
  foo = 42
  break
end

foo                             # => 42

But when we convert the loop to use the #loop method, this no longer works. Ruby can't find the block-local variable in the outer context.

loop do
  foo = 42
  break
end

foo                             # =>

# ~> NameError
# ~> undefined local variable or method `foo' for main:Object
# ~>
# ~> xmptmp-in254795zM.rb:6:in `<main>'

This difference probably isn't going to have an effect on most programs. But I instinctively prefer the tidiness of variables that don't "leak" outside of the loop context. After all, most such variables are created in order to hold intermediate values inside the loop proper, and so using them after the loop ends is probably a result of a mistake.

If there really is a variable we want to set within the loop and then use afterwards, we can always explicitly declare the variable before the loop starts. Then Ruby will assign to the existing outer variable, rather than making one that's local only to the block.

foo = nil
loop do
  foo = 42
  break
end

foo                             # => 42

I find this to be more intention-revealing, which was the whole point of switching to the #loop method in the first place.

Anyway, that's the #loop method, and that's all for today. Happy hacking!

Responses