String#toFunction for Prototype

As everybody is no doubt aware, there are new rules for getting a patch into Rails. With that in mind, I thought I’d share some of my patches here in the hopes that one of my army of readers (ha!) will find something I wrote interesting enough to try out for themselves. I’ve got a few Rails patches I want to talk about, but today I’m announcing a Prototype patch (Prototype is part of the Rails project) that I just submitted: ticket 9611.

Those of you who use Rails and Ruby will know all about Symbol#to_proc, that bit of magic that lets you write names = users.collect(&:name) rather than the more verbose

names = users.collect { |user| user.name }

I know, a simple example, but in some situations this improves readability like you would not believe. I wanted a similar thing for JavaScript, and for the example above, Prototype does just fine: users.pluck('name') is equivalent to

users.collect(function(user) { return user.name; })

But what about all the other Enumerable methods, and indeed collect itself – why can’t it handle this situation instead of us having to use pluck? So, taking inspiration from Ruby, I wrote a String#toFunction method that works with both properties and methods, just like in Ruby. (Not strictly true, since anything you expose using attr_accessor or some other getter method in Ruby is still just a message to the receiving object, just like all other method calls. My point is that I want the same semantic effect in JavaScript.) So, on with the code:

String.prototype.toFunction = function() {
  var property = this;
  return function(o) {
    var member = o[property];
    return (typeof member == 'function') ? member.apply(o) : member;
  };
};

What does this do? It returns a function that, given some object as an argument, returns the property of that object named by the string. So

radioInputs.findAll('checked')

is equivalent to

radioInputs.findAll(function(radio) { return radio.checked; })

(assuming we’ve modified the Enumerable methods slightly to check for a toFunction method on their inputs). What about that member.apply business? If it turns out that the property required is in fact a function, then that function is executed in the context of the object to which it belongs – member.apply(o). This means to can write things like $$('div').findAll('visible') rather than

$$('div').findAll(function(div) { return div.visible(); })

or even ['FOO', 'BAR', 'BAZ'].map('toLowerCase'). Pretty no? I know you can do that last one using invoke, and that invoke lets you pass arguments to the function. My point with all this is that these special ‘do something using a string’ methods shouldn’t be necessary – we can make all Enumerable methods accept strings with minimal effort. Wouldn’t you like to be able to users.sortBy('age')? I know I do.

If you think this is a good idea, why don’t you take a look at my patch and consider running the tests and giving me a +1 vote? Thanks a bunch.