Closures in Ruby, Part I

Closures are a powerful concept in some languages - a powerful tool in others. In my mind a tool is something I can ask for by name - as in, "I'd like a reciprocating saw, please." Languages where closures are truly a tool include Lisp, Python, Ruby, among others. For the time being I will use Ruby to discuss closures - although a switch over to Lisp may be necessary.

So what is a closure? A closure is the condition in which a function is provided serviceable access to its lexical environment (as it exists when the closure is created). For example a closure occurs when an anonymous function retains access to an instance variable of its defining class. For example this listing shows the canonical example ported to Ruby (from Perl):

def make_counter(start)
  return lambda do
    start += 1
    start
  end
end

from_ten = make_counter(10)
from_two = make_counter(2)
print from_ten.call   => 11
print from_two.call   => 3

Dan Muller did a fine job explaining the theory behind closures in this comment. If you have little idea what you might build with this tool - the best I can suggest is to fire up irb and test its behavior and boundaries.

So what defines tool-level support for closures? Perl, Lisp and Ruby - among others - have proper support for closures in that a single keyword or construct is used to define the closure. In Perl you use sub. In Lisp you use lambda. In Ruby though you can create closures a number of ways: lambda, Proc and blocks. Working through closures in Ruby led me to examine the (C) implementation of each construct. The following benchmarks were generated using my ClosureTest test fixture.

                               user      system    total     real
yield with block              0.030000  0.000000  0.030000  (  0.026955)
yield with block from proc    0.020000  0.000000  0.020000  (  0.029338)
yield with block from lambda  0.030000  0.000000  0.030000  (  0.029594)
call with block from lambda   0.050000  0.000000  0.050000  (  0.046879)
call with block from proc     0.050000  0.000000  0.050000  (  0.047232)
call with block               0.210000  0.000000  0.210000  (  0.209951)

I was not surprised by the cost of using a function object (Proc) - but I was surprised by two things: 1) the extra cost of lambda (over proc); and 2) the heavy cost of Proc/yield compared to other constructs. The moral of this story (Closures in Ruby, Part I) is: use blocks and yield wherever possible. Aside from being an elegant, timesaving construct - it is more performant than the alternatives.

AttachmentSize
closuretest.rb1.59 KB