Stevie Graham. Hacker at large. Working on Zap.

17th December 2009

Post with 5 notes

Ruby Idioms & Shortcuts: Symbol#to_proc

Say you have a collection of names in lowercase, names are proper nouns, so it’s proper grammar for them to be capitalised. How might one go about doing that in Ruby?

Calling a method on each member of a collection is a very frequently performed operation, and passing a block to an enumerator method just for this purpose gets very tedious fast.

Thanks to the dynamic nature of Ruby, one can open and extend classes at runtime. One of the neater bits of syntactic sugar I have seen is Symbol#to_proc. I believe it first appeared in Ruby Extensions, but is more widely known thanks to Ruby on Rails.

So instead of the above, we can now do the following instead:

Wow, that’s a cool shortcut, but how does it work?

First of all, look at the initial example. It might help to imagine that there are parentheses surrounding the block that is passed to the map method. It might make it a bit easier to understand when doing A-B comparisons between the first and second examples. As parentheses in Ruby are optional when they don’t add to the readability of the code, or the code won’t confuse the interpreter, they are usually omitted, as they were in this case.

Ok, lets walk through the second example. The map method is being called on an array literal containing the names of The Fab Four. The map method, from the module Enumerable that is “mixed in" to the Array class, invokes a block argument once for each element of self, in this case the array, and returns a new array containing the values returned by the block. However in this case, we don’t have a block, we have &:capitalize. What the hell is that? It’s another one of those things that make you think “Wow! Ruby is genius!”. When a unary ampersand is prepended to an object in Ruby, if the object is not a Proc object, the interpreter attempts to convert the object to a proc by calling to_proc on it. Since :capitalize is a Symbol, not a Proc, Ruby goes along and sends the to_proc message to :capitalize, thankfully both Rails and the Ruby Extensions project extend the Symbol class with a to_proc or at this point things would blow up in your face in a horrible mess.

So how does it work? Let’s look at the Rails implmentation:

Admittedly it’s not the most readable piece of code, so let me explain how it works

    1. A new Proc object is instantiated.
    2. The first argument, the object passed to the block by the enumerator is called on, in our exmaple one of the names, is removed from args
    3. The symbol itself is sent as a message with the remaining arguments to the object. There aren’t any in our example, but this is what makes it possible to do the magic with inject.

Rails’ implementation is more complex than you might require, allowing multiple arguments to be passed with the method call allowing things like this:

While being cool, I would consider this to be a corner case use for Symbol#to_proc, the most common use case being the object.enumerator(&:method) idiom. Unfortunately, the additional complexity of the implementation makes it slow as f***.

If you don’t need the additional functionality of passing multiple arguments, you can monkey patch Symbol with this:

You’ll find the Rails implementation over 3 times slower than using a block, but this version is only marginally slower than using a block.

As of Ruby 1.9, Symbol#to_proc is native Ruby. It has also been back ported to 1.8.7 but you might not be aware, if as I did you thought that the default Ruby docs targeted the latest release of that branch. In fact the docs cover 1.8.6!

I’m not sure if the native implementation supports multiple arguments a la Rails, but if it does I hope the performance is an improvement on the Rails implementation.

There you have it ladies and gents! Symbol#to_proc, a tasty piece of sugar!

Tagged: coderubyrailsruby-on-railsror

  1. rubydailystream reblogged this from stevegraham
  2. codelogic reblogged this from stevegraham
  3. stevegraham posted this