Following article provides a good explanation on the use of '&' in Ruby:
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.:
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:
def hello
yield if block_given?
end
hello do
puts "hello"
end
This time we get some output:
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.
def hello
yield if block_given?
end
blah = -> {puts "lambda"}
hello(&blah)
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.
def hello
yield if block_given?
end
class FooBar
def to_proc
-> {puts 'converted lambda'}
end
end
hello(&FooBar.new)
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:
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:
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:
def hello(&foo)
foo.call if block_given?
end
hello do
puts "hello"
end
Having said that; ‘block’ is a good convention.