Sunday, November 25, 2007

Loving to_proc

Anytime I'm pairing with a developer who isn't familiar with ActiveSupport's Symbol#to_proc extension, it's fun to see their reaction when they see how it works. It's the most radical example I've seen of a simple (and pure-Ruby) extension to a core class allowing for awesome improvements in readability.

[If you're not familiar with the method, read about it here or here. For a bizarre step beyond (that I'm not personally fond of) take a look at this old Dr. Nic post.]

The one frustrating thing about it is that it's so limited: you can only send one method with no arguments to each yielded object. If you need to pass arguments or do any more complex calculation, you're back to passing an associated block the old-fashioned way.

A couple of weeks ago Patrick and I were pairing and noticed a beneficial side-effect of the limitations of Symbol#to_proc: sometimes when you can't use it right off the bat, it's because the behavior you were going to put in the block would be better off living in the objects you're working with.

Here's a simple example. Imagine you want to expose the area codes represented in a collection of phone numbers. Your initial implementation might look like this.

def area_codes
  self.phone_numbers.collect do |phone_number|
    phone_number[0..2]
  end.uniq
end

"Shame that block's so ugly," you might think. Well you're right! It is a shame, and it doesn't have to be that way. It's ugly because it knows about the guts of phone numbers. If phone numbers knew more about themselves, using them could be prettier.

def area_codes
  self.phone_numbers.collect(&:area_code).uniq
end

Of course sometimes you really need to pass arguments. We toyed around with introducing an Array#to_proc that looked like this.

Array.class_eval do
  def to_proc
    lambda {|target| target.send *self}
  end
end

[1,2,3,4,5].select &[:between?, 2, 4]  # => [2, 3, 4]

In the end we decided that, while nicely brief, it wasn't pretty enough to put into our code base -- too much punctuation -- and stuck with the old-fashioned way.

Are there any other core extensions that have really floated your boat? Do share.

3 comments:

mdub said...

to_proc is indeed handy. I like your point about pushing the innards of blocks back onto the receiving objects.

FWIW (probably not very much) I once contrived an extension that makes possible such Groovy voodoo as

[1,2,3,4,5].select(&its.between?(2,4))

see MethodMissingMagic

Anonymous said...

I also like Symbol#to_proc but when you start supporting anything beyond basic method dispatch, you can start to affect performance which I don't find very acceptable for some syntatic sugar. One approach is to memoize the proc and keep it simple, preferring the use of an actual block for edge cases. I've written an article about this on my blog.

Anonymous said...

Thats http://lukeredpath.co.uk by the way.