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 for Tempfile#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

Comments

  1. Sam Smoot said about 3 hours later:

    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.

  2. Dr Nic said about 9 hours later:

    Hmm, I don’t think I use the following syntax barely enough:

    @foo =
      begin
        ... do something ...
      end
    

(leave url/email »)