Computer >> Computer tutorials >  >> Programming >> ruby

The clever hack that makes `items.map(&:name)` work

When iterating over arrays, there's one piece of shorthand that I find myself using again and again. It's the &: trick, aka the "ampersand colon" or "pretzel colon". In case you're not familiar with it, here's how it works:

words = ["would", "you", "like", "to", "play", "a", "game?"]

# this...
words.map &:length

# ..is equivalent to this:
words.map { |w| w.length }

Until recently, I had assumed that the &: syntax was an operator. But it's not. It's a clever hack that started out in ActiveSupport and became an official feature in Ruby 1.8.7.

The & operator

In addition to being used for AND logic operations, the "&" character has another use in Ruby. When added to the beginning of a method argument, it calls to_proc on its operand and passes it in as a block. That's a mouthful. It's much simpler to see the example:

def my_method(&block)
  block.call
end

class Greeter
  def self.to_proc
    Proc.new { "hello" }
  end
end

my_method(&Greeter) # returns "hello"

Symbol#to_proc

You can add a to_proc method to any object, including Symbol. That's exactly what ruby does to allow for the &: shortcut. It looks something like this:

class Symbol
  def to_proc
    Proc.new do |item|
      item.send self
    end
  end
end

Clear as mud? The important part is item.send(self). Self, in this case refers to the symbol.

Putting it all together

Enumberable methods like each and map accept a block. For each item they call the block and pass it a reference to the item. The block in this case is generated by calling to_proc on the symbol.

# &:name evaluates to a Proc, which does item.send(:name)
items.map(&:name)

The interesting thing about this is that map  doesn't  know any of this is going on!  The bulk of the work is being done by the :name symbol. It's definitely clever...almost too clever for my taste. But it's been a part of Ruby's standard library for years at this point, and it's so handy I doubt I'll stop using it for now. :)