Ruby Blocks as Closures
Responding to Pat Eyler’s February Question.
PHP pushed me to Ruby. I just got sick of its idiosyncrasies. After writing
str_replace() for the 100th time why can I still not remember whether needle comes before haystack or vice-versa? Who, in their infinite delusions, thought that something like
magic_quotes_gpc could ever be beneficial to web app security? Why are references so damn wonky? It’s not like other languages don’t have their idiosyncrasies, but PHP’s seem so arbitrary. Almost as if the language was grown in a petri dish rather than designed according to a vision.
I have respect for the vision behind languages like Java, C++, Perl, and especially Lisp. However Ruby was the first language that felt right. Feeling good is a big part of programming for me, and is the reason I don’t work for a large corporation. The feel-good aspects of Ruby are numerous. The syntactic sugar available is pretty nice whether you’re looking at the built-ins or community stuff like ActiveSupport in Rails. Whenever I’m writing PHP I pine for a million little things like ranges, or chained methods, or
Fixnum::days. But those are just conveniences, they don’t denote anything fundamentally more powerful about Ruby (directly anyway).
The first big eureka moment for me came when I grokked closures ala blocks. Functions are probably the single most important invention in the history of applied computer science. They are the basic building block of abstraction, without which complex software would not be possible. Functions without closures are top-down abstraction. That is, you abstract away a well-defined task along a well-defined set of parameters. Closures give you sort of “middle abstraction”; you can abstract the mechanics of some function while leaving the specific semantics and parameters to be defined at call-time. I first learned about closures from my Scheme class during my undergrad. They explained the concept of an environment bound to a function, but the implications didn’t sink in until I learned Ruby.
Take for instance the find method of the Enumerable module. This is a very simple method in Ruby. What it does is to define the algorithm of looping over the set of elements in the object to find a match. The key here is that the definition of a match is fully abstracted. When
find is called, the programmer specifies a block that defines the semantics of a match. So if the block were just an anonymous function as is available in PHP you could put any code you wanted in there, but the only variable that would truly be available would be the element you were comparing against. Now you could print the value of local variables into the function definition in a cumbersome approximation of closure behavior (assuming PHP had a
find similar to Ruby’s):
$a = array(1,2,3,3,4,5); $mod_value = dynamic_mod_value(); $f = create_func('$v','return $v % '.$mod_value.' == 0;'); find($a,$f); //$f is the function trying to be a block.
Ignoring the cumbsersome syntax, this does the same thing as the example in the Ruby docs. However it can’t come close to the real power of a closure illustrated in the following contrived example:
a = [1,2,3,3,4,5] previous = nil a.find do |v| equal = v == previous previous = v equal end
This code returns the first duplicate element in a sorted array by making dynamic use of the environment bound to the block. The readable syntax makes it look like looping constructs in other languages, thus masking the fact that
find is a regular ruby method. Those who don’t grok closures insist that PHP can do this just as easily using
foreach, while missing the fact that this is more akin to writing your own version of
foreach. The problem with such a simple example is the temptation to subvert the specific functionality to old paradigms. The problem with a more complex example is that the context could overwhelm the simple elegance of closures. Either way it’s one of those things you don’t grok until you grok.
Closures may pale in comparison to the amount of power granted by mixins and other dynamic Ruby functionality such as
method_missing?, however they are also a more pure benefit. After all, Ruby provides whole new paradigms of application architecture (eg. why can’t Rails clones in PHP approach the original?), but all that dynamic code brings a certain responsibility. Debugging dynamic code can be tricky, and there are always those who would rather type a little more (aided by IDEs) than think a little harder. Maybe some day I’ll wax philosophical about the joys of dynamic languages, but for now I’m still pretty impressed with the simple elegance of closures.