Ruby's Hidden do {} while () Loop
Posted by Jeremy Voorhis Tue, 12 Jun 2007 22:13:00 GMT
I found the following snippet while reading the source forTempfile#initialize in the Ruby core library:
begin
tmpname = File.join(tmpdir, make_tmpname(basename, n))
lock = tmpname + '.lock'
n += 1
end while @@cleanlist.include?(tmpname) or
File.exist?(lock) or File.exist?(tmpname)
At first glance, I assumed the while modifier would be evaluated before the contents of begin...end, but that is not the case. Observe:
>> begin
?> puts "do {} while ()"
>> end while false
do {} while ()
=> nil
As you would expect, the loop will continue to execute while the modifier is true.
>> n = 3
=> 3
>> begin
?> puts n
>> n -= 1
>> end while n > 0
3
2
1
=> nil
While I would be happy to never see this idiom again, begin...end is quite powerful. The following is a common idiom to memoize a one-liner method with no params:
def expensive
@expensive ||= 2 + 2
end
Here is an ugly, but quick way to memoize something more complex:
def expensive
@expensive ||=
begin
n = 99
buf = ""
begin
buf << "#{n} bottles of beer on the wall\n"
# ...
n -= 1
end while n > 0
buf << "no more bottles of beer"
end
end

Just a warning, but I heard tell on comp.lang.ruby once upon a time that this (post-conditional-loops) was to be removed in 2.0 in favor of flow-control within your loop.
That was probably the better part of a year ago though, and hell if I can find the thread.
So I’ve avoided it since then, but maybe prematurely, since comp.lang.ruby seems loaded with threads on the construct with nary a whisper of deprecation. Count me as a fan of the post-conditional loop.
Hmm, I don’t think I use the following syntax barely enough:
@foo = begin ... do something ... end