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.
| Attachment | Size |
|---|---|
| closuretest.rb | 1.59 KB |
Comments
Hi!
Sorry for being so delayed in posting here but life, vacation, child illnesses etc kept me from it :)
This is pretty interesting stuff. Before this post I hadn't spent time looking into the performance characteristics of the different ways to do closures in Ruby. I was just loving closures in general and how easy of a tool they are to use in Ruby. :)
So I took your code and ran it on my machine. I happen to be running on a MacBook pro and have 2 Ruby vms. JRuby 1.1 RC2 and the CRuby vm everyone uses. It's interesting to note that in JRuby
CRuby numbers on my machine:
While in JRuby the numbers seem less clear, I still think the elegance of yields and blocks needs to be a factor until such a time as performance tuning needs to be examined.
Also I hadn't been using yield all that much but I haven't written to many functions that take a block explicitly, yet. I'm still getting into that swing. But I'll be sure to go back and make sure I'm using yield :)
good stuff!
Jay,
Thanks a ton for extending the usefulness of this post.
Once you open your mind to the concept of closures, whatever the syntactical sugar may be, you begin to find all sorts of applications. I have one concern about closures: they lower the barrier of entry on concurrent programming. In other words, I am concerned because closures make it so a developer can be writing concurrent code, without even knowing it. I hope any senior developers out there such as yourself remind their team that closures can be a loaded gun.
It is certainly great to see dynamic language support making its way onto the .NET Runtime playing field. I wonder how far off we are from having (Visual Studio) solutions that contain both C# projects and Ruby.NET (or whatever it gets called)? The thought sits just fine in my mind because each language has its own strengths and weaknesses.
Thanks again Jay.