Recently I was asked a question about ‘& parameters’ when you define and/or call methods which take a block e.g.:
|
|
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.:
|
|
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:
|
|
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
|
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
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
world |
Passing The Block Around
We now know enough to easily understand our first example:
|
|
- we define
blah
to expect a block, insideblah
we can refer to this block asblock
- we define
yadda
to expect one parameter, this parameter would be referred to asblock
insideyadda
, but it is not a block in that we could notyield
to it insideyadda
foo
also expects a block and we can refer to this block asblock
insidefoo
- when we call
yadda
from withinblah
we pass it ourblock
variable without the ampersand, sinceyadda
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 aProc
object - when we call
foo
from insideyadda
we pass it ourblock
variable, but this time with an ampersand sincefoo
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:
|
|
We know that this is equivalent to:
|
|
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