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

The &: trick is a great shortcut when using enumerable methods like map. The way it works may surprise you. In this post we'll look in detail at exactly how code like users.map(&:name) functions under the hood.

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. :)

What to do next:
  1. Try Honeybadger for FREE
    Honeybadger helps you find and fix errors before your users can even report them. Get set up in minutes and check monitoring off your to-do list.
    Start free trial
    Easy 5-minute setup — No credit card required
  2. Get the Honeybadger newsletter
    Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo

    Starr Horne

    Starr Horne is a Rubyist and Chief JavaScripter at Honeybadger.io. When she's not neck-deep in other people's bugs, she enjoys making furniture with traditional hand-tools, reading history and brewing beer in her garage in Seattle.

    More articles by Starr Horne
    Stop wasting time manually checking logs for errors!

    Try the only application health monitoring tool that allows you to track application errors, uptime, and cron jobs in one simple platform.

    • Know when critical errors occur, and which customers are affected.
    • Respond instantly when your systems go down.
    • Improve the health of your systems over time.
    • Fix problems before your customers can report them!

    As developers ourselves, we hated wasting time tracking down errors—so we built the system we always wanted.

    Honeybadger tracks everything you need and nothing you don't, creating one simple solution to keep your application running and error free so you can do what you do best—release new code. Try it free and see for yourself.

    Start free trial
    Simple 5-minute setup — No credit card required

    Learn more

    "We've looked at a lot of error management systems. Honeybadger is head and shoulders above the rest and somehow gets better with every new release."
    — Michael Smith, Cofounder & CTO of YvesBlue

    Honeybadger is trusted by top companies like: