Ruby & Ampersand

The ampersand (&) operator in Ruby programming has a variety of uses, but in my albeit short career as a Rubyist I rarely find myself calling on it.

I’m content to mix in && when appropriate, but the unary & eludes me. Why haven’t I found more opportunities to make the & operator more useful? Could it come in handy more often while I continue learning Ruby? What does it even do?

This is my first blog about Ruby programming. In it we will focus on a deliberately narrow application of this Ruby feature. The list of blog posts toward the bottom of this post should help if you want to explore more about this topic. As usual, a Google search is also a good place to go.

BEFORE WE GET STARTED

We will explore the unary & operator in this blog. In Ruby there is also what’s called a binary, or bitwise & operator. To learn more about those, consider reading these blogs:

  1. Ruby’s Bitwise Operators” by Calle Erlandsson
  2. Ruby Bitwise Operators” by Mehdi Farsi

Note: there is also this operator: &. known as the “safe navigation” operator. For more information on that, read The Safe Navigation Operator (&.) in Ruby by Georgi Mitrev.

A QUICK PRIMER ON BLOCKS

Every Ruby block is constructed in one of two ways, either enclosed in curly braces { }:

names = ["Jackie", "Lisa", "Phil"]

names.each { |name| puts "Hello, #{name}" }

# => Hello, Jackie
# => Hello, Lisa
# => Hello, Phil

…or enclosed in the do and end keywords:

names = ["Carlton", "Ashley", "Will"]

names.each do |name|
  puts "Hello, #{name}"
end

# => Hello, Carlton
# => Hello, Ashley
# => Hello, Will

BY THE WAY

Does string interpolation #{ } count as a “block?” It does have curly braces, after all. Despite finding no evidence to support this assumption, I would venture to say that no, string interpolation does not count as a block. For now let’s just say string interpolation has its own thing going on.

Who doesn’t love a good block?

BACK TO OUR REGULARLY SCHEDULED PROGRAMMING

Consider the following example:

class Person
  ALL = [ ]
  attr_reader :name

  def initialize(name)
    @name = name
    ALL << self
  end

  def self.all
    ALL
  end
end

Jackie = Person.new("Jackie")
Lisa = Person.new("Lisa")
Phil = Person.new("Phil")
Carlton = Person.new("Carlton")
Ashley = Person.new("Ashley")
Will = Person.new("Will")

We can call enumerable methods on our array of objects:

Person.all.map do |person|
  person.name
end

# => ["Jackie", "Lisa", "Phil", "Carlton", "Ashley", "Will"]

With & we can accomplish the same goal:

Person.all.map(&:name)

# => ["Jackie", "Lisa", "Phil", "Carlton", "Ashley", "Will"]

Maybe you’re a little bit clearer about what’s going on here. But the story behind how this works is sort of complicated. Before we can figure out what’s happening, first we should go over some background…

PROCS

Proc is a predefined class built in to Ruby. Like with all Ruby classes we can create instance objects of the Proc class. Unlike other classes, however, we can also associate a block with a Proc object and then assign that object to a variable. Let’s look at how this happens using our previous example of Person.all:

person_printer = Proc.new { |p| p.name }

# person_printer holds on to the block { |p| p.name }.
# Using the & operator, we can now use person_printer to pass
# the block to a method like Array#map.

Person.all.map(&person_printer)

# => ["Jackie", "Lisa", "Phil", "Carlton", "Ashley", "Will"]

That’s kind of cool. It’s not hard to imagine having class instances with a lot of attributes and needing to iterate over collections of those objects with complicated methods. With a Proc we can save those bigger, repetitive tasks for later use and clean up our code at the same time. But we still don’t know exactly what’s going on behind the scenes…

Caution – Stumbling Ruby newbie outside his natural habitat

HERE’S HOW I SEE IT

Note: there is much more to Procs (including something called a lambda) than is covered here. For our purposes, the background information above should be enough. For more, consult the links toward the bottom of this post.

In the examples we looked at here, there are two separate but similar things happening:

  1. In our person_printer example, Ruby sees the & operator and knows that person_printer is an instance of a Proc. Thus it can pass that variable to the Array#map enumerable as a block.
  2. In our Array#map(&:name) example, Ruby calls the #to_proc method on the symbol :name, which is essentially a reader method for our Class instance attribute. Then it converts that symbol/reader method into a block and passes the block to the Array#map enumerable.

Ruby lets us solve a problem different ways and still end up with the same result.

If you want to wield the & like a wizard’s wand, check out these links:

  1. The & Operator in Ruby by Pan Thomakos
  2. Understanding Ruby’s idiom: array.map(&:method) by Brian Storti
  3. Ruby Procs And Lambdas (And The Difference Between Them) by Alan Skorkin
  4. Procs vs. Lambda by Jim Weirich via Mislav Marohnić
  5. Understanding Ruby Blocks, Procs and Lambdas by Robert Sosinski

WRAP IT UP

In using the & operator, are we really saving ourselves from a ton of extra work? With our simplified examples, obviously not. We didn’t reduce that much typing of code and we used a strange and unintuitive technique to accomplish a straightforward task.

On the other hand, using the & operator does result in more concise code. I can see where using this shortcut might be nice. I will look for more opportunities to use the & operator in my code and I hope, with practice, it becomes a more useful tool in my Ruby repertoire.

Leave a comment