A while back, I wrote an introduction on code blocks. So if you’re not sure what they are, you should read it first.
Something I find interesting with code blocks is that they create the illusion of being executed at the moment they are written. It’s easy to get fooled, because when you look at code like this :
the_array.each {|item| puts item}
what’s between the curly braces surely doesn’t look like a parameter passed to the each method. However that’s exactly what’s happening. While the following example would fail (Array each method doesn’t expect any regular parameter), for understanding purpose we could write it this way :
my_block = Proc.new {|item| puts item}
the_array.each(my_block)
Written that way, it becomes clear that you pass the control over to the each method, letting it manage everything. That being said, most of the time you won’t be able to pass a block that way since most methods expect raw blocks… not Proc objects passed as regular parameters.
Raw blocks and Proc objects
Blocks come in two flavors : Raw and Proc objects. A raw block is not an object, it’s only a chunk of code. Proc objects are blocks wrapped into objects. With a Proc object, your block becomes something more concrete and allows you to invoke methods on it like with any other object. To invoke the block contained inside a Proc object, you have to use it’s “call” method instead of the global yield keyword which is used to invoke raw blocks.
Passing the block
The most usual way to pass a block to a method is to use it’s “raw” form, like so :
@greasy_food_you_eat_on_fridays.each do |meal|
puts meal.name
end
When you do so, Ruby does some magic to transmit the block to the receiving method. More about that in a few more paragraphs.
You can also pass a block as a regular parameter to the receiving method by converting it to a Proc object like we did earlier, but for this to work the receiving method needs to expect the block as a regular parameter.
code_block = Proc.new { |meal| puts meal.name }
@greasy_food_you_eat_on_fridays.each(code_block)
If the method each is implemented in a way to receive a parameter of class Proc, the code will pass. Else, it will fail. There is no more magic when you pass a block that way.
Receiving the block
if you passed a raw block to the receiving method, that method has 2 ways to deal with it.
- Use the raw block directly
- Convert the raw block into a Proc object and use it
if we choose the first option, we can invoke the block with yield :
def some_method()
yield if block_given?
end
As you can see, there is some magic happening here. We don’t see the block in the method definition, but somehow it is there and Ruby knows it. block_given? returns true or false depending if a raw block has been passed or not (we don’t want to invoke something that doesn’t exists). yield invokes the raw block.
Now if we choose the 2nd option, it looks like that :
def some_method(&block)
block.call unless block.nil?
end
Again, some magic happened here. If the LAST specified parameter of the receiving method is prefixed with an ampersand, ruby will take the received raw block and convert it into a Proc object. Then, it will assign it to this last parameter. Note that the ampersand sign doesn’t mean “passed by reference” like in some other languages. It is just a character used by Ruby to determine if it must convert a raw block into a proc object. If that bit of information didn’t exist, Ruby wouldn’t know what you are trying to do and “block” would become a parameter like any other one.
#UPDATE 05/30/2007
Ah-ha! Shadowfiend and rx made me realize something. Remember that piece of code ?
> 1. my_block = Proc.new {|item| puts item}
> 2. the_array.each(my_block)
I said it would fail since the “each” method isn’t expecting a normal parameter. Well it’s true… but you can bypass this lil problem by prefixing my_block with an & sign when you call the method, like that :
> 1. my_block = Proc.new {|item| puts item}
> 2. the_array.each(&my_block)
Tada! Thanks for the precision!
What to expect in the next part :
- Code blocks and scope
- lambdas
your second example, given as
> 1. my_block = Proc.new {|item| puts item}
> 2. the_array.each(my_block)
would fail, right. fix that by adding a `&’ to the call parameter:
2. the_array.each( &my_block )
^
using this syntax, you actually _can_ foist Proc objects on methods expecting only raw blocks.
…the pickaxe is your friend. 😉
I believe in order to pass a block as an &-prefixed parameter, you have to prefix it with an &, as well. So:
@greasy_food_you_eat_on_fridays.each(code_block)
Would instead be:
@greasy_food_you_eat_on_fridays.each(&code_block)
Moreover, you should still be able to use yield in those situations. You should also be able to use block_given? in either situation instead of comparing to nil, which I find clearer.
Nice post 🙂
rx and Shadowfiend,
You’re both right! I’m going to update the original post about that.
rx, sorry. I don’t know why your comment got marked as spam by Askimet.