&lified

Posted by # May 8th 04:51 PM

UPDATE Now ampersand even has its own blog.

Ampersand has received a lot of press lately and I decided to put my oar in as well.

My recent history with ampersand is kind of embarrassing, to say the least. I was giving a Ruby class in UK a few years back and during a class, I asked a participant whether there is some other term for the at sign in English. “Sure”, she said, “it’s ampersand.” To this day I’m not sure whether she thought I said et sign, or genuinely mixed up asperand and ampersand. But for quite some time after that, I happily treated @ as ampersand.

But neither linguistics or typography is the topic of today’s article. Let’s rather talk about ampersand (the real one) in Ruby.

In Ruby, an ampersand denotes a block parameter. But let’s not get ahead of ourselves. A little background might be in place.

One of the coolest features in Ruby are code blocks, or closures. They are basically anonymous functions, but as pretty much everything in Ruby, they are also objects. They are anonymous functions, that have little use just by themselves. However, you can turn them into Proc objects either with the Proc.new constructor or using the lambda kernel method. The common best practice to mark code blocks is to use the curly braces with one-liners and do..end with blocks that span multiple lines:

yell = lambda { puts "AAARGH!!!" }
whisper = Proc.new do
 puts "whee" 
end
yell.call
whisper.call

➥

AAARGH!!!
whee

Maybe the most useful application of Proc objects in Ruby are block parameters. Anyone with some knowledge of Ruby is familiar with the following:

>> arr = %w(apple orange kiwi)
=> ["apple", "orange", "kiwi"]
>> arr.each {|i| puts i[0,2]}
ap
or
ki
=> ["apple", "orange", "kiwi"]

As you can see, in this case you need to use neither Proc.new nor lambda with the block; it’s converted to a Proc object implicitly. But as the method receives just a Proc object as its parameter, you could also say something like this, right:

>> put_two = lambda {|i| puts i[0,2]}
=> #<Proc:0x00329060@(irb):41>
>> arr.each(put_two)

Err, not quite. It turns out Ruby methods can take two kinds of parameters—a number of normal parameters and a block parameter. In the code above the interpreter will think that the put_two Proc object is passed as a normal parameter to the each call, and since each doesn’t take any normal parameters, you will get an error:

>> arr.each(put_two)
ArgumentError: wrong number of arguments (1 for 0)
    from (irb):42:in `each'
    from (irb):42

And this is what brings us back to the ampersand:

>> arr.each(&put_two)
ap
or
ki
=> ["apple", "orange", "kiwi"]

So the ampersand is used to tell the interpreter that the following reference is the block parameter of the method.

One fairly common idiom in the Rails world is this kind of construct:

>> arr.map(&:length)

It is effectively the same as

>> arr.map {|i| i.length }
=> [5, 6, 4]

However, if you start a plain irb session, you will notice something isn’t quite right:

>> arr.map(&:length)
TypeError: wrong argument type Symbol (expected Proc)
    from (irb):2

That’s right. The cool shorthand method that worked so nicely in your Rails app doesn’t work in plain Ruby. That’s because there is some Rails magic behind the &:method call. This magic is e.g. the reason why Ezra has prohibited using the shortcut in Merb framework code.

But let’s have a closer look at what’s actually happening behind the scenes in the shortcut. The thing that’s different from our earlier calls is that there is a colon between the ampersand and the word “length”. This means that we’re not using a variable or method called length, but the symbol :length. If you’re not familiar with Ruby symbols (or even if you are), reading Josh’s recent article on symbols is a worthwhile read.

Now that we know that we’re trying to pass a symbol as the block parameter to a method (and that it’s not really working, as the error above indicates), we need a way to convert it to a Proc object like expected by the method. Ruby has a slew of type conversion methods that are called implicitly whenever it’s clear that a certain type of object is needed. Inside a string for example, to_s is called automatically for every object that is not a string itself:

>> "Nice array: " + arr
TypeError: can't convert Array into String
    from (irb):6:in `+'
    from (irb):6
>> "Nice array: #{arr}" 
=> "Nice array: appleorangekiwi" 
>> "Nice array: " + arr.to_s
=> "Nice array: appleorangekiwi"

In the same vein, since a block parameter of a method needs to be a Proc object, to_proc is called automatically for all other objects in an effort to get a hold of a proc. So could it work if we just added a to_proc method to the Symbol class? Let’s find out!

class Symbol
  def to_proc
    lambda {|i| i.send(self)}
  end
end

Here we make to_proc a lambda function that will use the send method to call the method with the same name as the Symbol object in question (self) for the element that’s passed to it. (That got too confusing so let’s just use examples). So

arr.send(:length)

is the same as

arr.length

And thus

:length.to_proc

would become

lambda {|i| i.send(:length)}

Now, let’s see how our new method performs:

>> arr.map(&:reverse)
=> ["elppa", "egnaro", "iwik"]

Perfect!

While Symbol#to_proc is a clever and perhaps an elegant hack, it’s still kind of a hack. So should you use it in your code? I tend to side with Ezra on this. If you’re writing framework code, you should probably err on the side of readability and common usage, and thus avoid “magical shortcuts” like these. But in application code, why not. I certainly do.

Jump to comment form

Comments

  1. Michael D. Ivey 05.08.08 / 20PM

    It’s not really true that it’s banned from Merb framework code because it’s magic. It’s banned from Merb framework code because it is slooooowwwww. The magic is kinda a side effect. :)

    I believe Ruby 1.9 has a Symbol#to_proc in some kind of native implementation, which is faster but still not equivalent to the inline block.

    That being said, it’s a fantastic idiom that is very expressive, so I hope that we will eventually have a version of it that performs well.

  2. Stephen Bartholomew 05.08.08 / 21PM

    Just to clarify, I’ve never heard an @ sign being called ampersand and I’m from the UK.

    The # symbol however is called ‘hash’ here, not ‘pound’ – which I’ve heard/seen used in the US. This one is particularly confusing for us as ‘pound’ is our curenncy ‘£’.

    Enough on words – great article :-)

    Steve

  3. Jarkko 05.09.08 / 09AM

    @Michael

    Thanks for the clarification, I should’ve probably said “one of the reasons”. The magicality however is the context where Ezra mentions Symbol#to_proc, both in MountainWest RubyConf and GoRuCo: “Prefer simplicity over magic as much as possible”. Speed is definitely an integral part of that, though.

    @Stephen

    Thanks for the confirmation :-)

  4. Piers Cawley 05.09.08 / 18PM

    I take the view that all’s fair if the magic’s sufficiently encapsulated and client code becomes more expressive. Code like:

    collection.reject(&:nil?).collect {|each| ... }
    

    seems simpler and clearer to me than:

    collection.reject {|each| each.nil?}.collect {|each| ... }
    

    If profiling shows that the expressive form is slowing things down, I’d probably end up rewriting it to:

    res = []
    collection.each do |each|
      unless each.nil?
        res << ...
      end
    end

    Which is undoubtedly faster, arguably less magical, and definitely more complicated. If I wanted to write for loops, I’d be writing in a language that doesn’t have each, collect, inject and all the other expressive methods for working with collections.

  5. Phil 05.09.08 / 19PM

    They are basically anonymous functions, but as pretty much everything in Ruby, they are also objects.

    Actually, not really. Blocks are not objects. You use blocks to create Proc objects, but blocks can exist on their own without being instantiated as objects. To top it off, lambda and Proc.new do not even act the same. This article does a good job of explaining the differences:

    http://www.neeraj.name/589/block-vs-lambda-vs-proc

  6. Jarkko Laine 05.09.08 / 21PM

    @Piers

    Agreed.

    @Phil

    Good call, I should have been more careful in my simplification. Why lambda and Proc.new behave differently escapes me, especially since there’s no distinction between the two (as far as I know) if you try to inspect the resulting objects.

Unobtrusive Prototype book

Recently

RailsConf 2007 Speaker

Beginning Ruby on Rails E-Commerce