Idea

Recently I was asked a question about ‘& parameters’ when you define and/or call methods which take a block e.g.:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def blah(&block)
  yadda(block)
end

def yadda(block)
  foo(&block)
end

def foo(&block)
  block.call
end

blah do
  puts "hello"
end

As you pass this parameter around, sometimes the ampersand appears in front of it, but other times it doesn’t, seemingly with no rhyme of reason. As we dig into crazy metaprogramming or write various libraries, it is often hard to remember how confusing Ruby can be when you’re starting out. So, let’s dig into this a little more deeply and shed some light on what’s going on.

The Implicit Block

Methods in Ruby can take arguments in all sorts of interesting ways. One case that’s especially interesting is when a Ruby method takes a block.

In fact, all Ruby methods can implicitly take a block, without needing to specify this in the parameter list or having to use the block within the method body e.g.:

1
2
3
4
5
6
def hello
end

hello do
  puts "hello"
end

This will execute without any trouble but nothing will be printed out as we’re not executing the block that we’re passing in. We can – of course – easily execute the block by yielding to it:

1
2
3
4
5
6
7
def hello
  yield if block_given?
end

hello do
  puts "hello"
end

This time we get some output:

1
hello

We yielded to the block inside the method, but the fact that the method takes a block is still implicit.

It gets even more interesting since Ruby allows to pass any object to a method and have the method attempt to use this object as its block. If we put an ampersand in front of the last parameter to a method, Ruby will try to treat this parameter as the method’s block. If the parameter is already a Proc object, Ruby will simply associate it with the method as its block.

1
2
3
4
5
6
7
def hello
  yield if block_given?
end

blah = -> {puts "lambda"}

hello(&blah)
1
lambda

If the parameter is not a Proc, Ruby will try to convert it into one (by calling to_proc on it) before associating it with the method as its block.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def hello
  yield if block_given?
end

class FooBar
  def to_proc
    -> {puts 'converted lambda'}
  end
end

hello(&FooBar.new)
1
converted lambda

All of this seems pretty clear, but what if I want to take a block that was associated with a method and pass it to another method? We need a way to refer to our block.

The Explicit Block

When we write our method definition, we can explicitly state that we expect this method to possibly take a block. Confusingly, Ruby uses the ampersand for this as well:

1
2
3
4
5
6
7
def hello(&block)
  yield if block_given?
end

hello do
  puts "hello"
end

Defining our method this way, gives us a name by which we can refer to our block within the method body. And since our block is a Proc object, instead of yielding to it, we can call it:

1
2
3
4
5
6
7
def hello(&block)
  block.call if block_given?
end

hello do
  puts "hello"
end

I prefer block.call instead of yield, it makes things clearer. Of course, when we define our method we don’t have to use the name ‘block’, we can do:

1
2
3
4
5
6
7
def hello(&foo)
  foo.call if block_given?
end

hello do
  puts "hello"
end

Having said that; ‘block’ is a good convention.

So, in the context of methods and blocks, there are two ways we use the ampersand:

  • in the context of a method definition, putting an ampersand in front of the last parameter indicates that a method may take a block and gives us a name to refer to this block within the method body
  • in the context of a method call, putting an ampersand in front of the last argument tells Ruby to convert this argument to a Proc if necessary and then use the object as the method’s block

Passing Two Blocks To A Method

It is instructive to see what happens when you try to pass a both a regular block and a block argument to a method:

1
2
3
4
5
6
7
8
9
def hello(&block)
  block.call if block_given?
end

blah = -> {puts "lambda"}

hello(&blah) do
  puts "hello"
end

You get the following error message:

1
code.rb:56: both block arg and actual block given

It is not even an exception – it’s a syntax error!

Using Another Method As A Block

It’s also interesting to note that since you can easily get a reference to a method in ruby and the Method object implements to_proc, you can easily give one method as a block to another e.g.:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def hello(&block)
  block.call if block_given?
end

def world
  puts "world"
end

method_reference = method(:world)

hello(&method_reference)
1
world

Passing The Block Around

We now know enough to easily understand our first example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def blah(&block)
  yadda(block)
end

def yadda(block)
  foo(&block)
end

def foo(&block)
  block.call
end

blah do
  puts "hello"
end
  • we define blah to expect a block, inside blah we can refer to this block as block
  • we define yadda to expect one parameter, this parameter would be referred to as block inside yadda, but it is not a block in that we could not yield to it inside yadda
  • foo also expects a block and we can refer to this block as block inside foo
  • when we call yadda from within blah we pass it our block variable without the ampersand, since yadda does not a expect a block parameter, but simply a regular method argument, in our case this regular method argument will just happen to be a Proc object
  • when we call foo from inside yadda we pass it our block variable, but this time with an ampersand since foo actually expects a block and we want to give it a block rather than just a regular variable

It should now be much more obvious why the ampersand is used in some cases, but not in others.

The Symbol To Proc Trick

We should now also have a lot less trouble understanding the ‘symbol to proc’ trick. You’ve no doubt seen code like this:

1
p ["a", "b"].map(&:upcase)

We know that this is equivalent to:

1
p ["a", "b"].map{|string| string.upcase}

But now we also make an educated guess as to why they are equivalent. We have a Symbol object (’:upcase’), we put an ampersand in front of it and pass it to the map method. The map method takes a block, and by using the ampersand we’ve told Ruby that we want to convert our Symbol object to a Proc and associate it with the map method as its block. It turns out that Symbol implements to_proc in a special way, so that the resulting block becomes functionally equivalent to our second example above. Of course these days Ruby implements Symbol#to_proc using C, so it’s not quite as nice looking as the examples you’ll find around the web, but you get general idea.

Anyway, hopefully this makes blocks and ampersands a bit more friendly. It’s definitely worth it to occasionally revisit the basics, I’ll try to do it more often in the future.

Image by Martin Whitmore