Semantics Def
When I was first getting started with Ruby, I heard a lot of talk about blocks, and how you could "cast" them to Procs by using the & operator when calling methods. Last week, in comments about my last post (Ruby is NOT a Callable Oriented Language (It’s Object Oriented)), I heard that claim again.
To be honest, I never really thought that much about it, and the idea of "casting" a block to a Proc never took hold in my mental model, but when discussing my post with a number of people, I realized that a lot of people have this concept in their mental model of Ruby.
It is not part of Ruby's semantics.
In some cases, Ruby's internal implementation performs optimizations by eliminating the creation of objects until you specifically ask for them. Those optimizations are completely invisible, and again, not part of Ruby's semantics.
Let's look at a few examples.
Blocks vs. Procs
Consider the following scenarios:
def foo yield end def bar(&block) puts block.object_id baz(&block) end def baz(&block) puts block.object_id yield end foo { puts "HELLO" } #=> "HELLO" bar { puts "HELLO" } #=> "\n\nHELLO"
Here, I have three methods using blocks in different ways. In the first method (foo), I don't specify the block at all, yielding to the implicit block. In the second case, I specify a block parameter, print its objectid, and then send it on to the baz method. In the third case, I specify the block, print out its objectid and yield to the block.
In Ruby's semantics, these three uses are identical. In the first case, yield calls an implicit Proc object. In the second case, it takes an explicit Proc, then sends it on to the next method. In the last case, it takes an explicit Proc, but yields to the implicit copy of the same object.
So what's the & thing for?
In Ruby, in addition to normal arguments, methods can receive a Proc in a special slot. All methods can receive such an argument, and Procs passed in that slot are silently ignored if not yielded to:
def foo puts "HELLO" end foo { something_crazy } #=> "HELLO"
On the other hand, if you want a method to receive a Proc in that slot, and thus be able to yield to it, you specify that by prefixing it with an &:
def foo yield end my_proc = Proc.new { puts "HELLO" } foo(&my_proc)
Here, you're telling the foo method that my_proc is not a normal argument; it should be placed into the proc slot and made available to yield.