Ruby Block Scope
-
Ruby’s blocks, or closures, are a feature that does not have a direct equivalent in PHP. We devote a fair number of pages to this topic in the book. Even so, it will take a bit of time and practice before you feel completely comfortable with them. Let’s take a look at an easy way that Ruby’s block scoping might trip you up.
In this example, we have an array of fruit. We want to iterate through the array and print the name of each fruit. At the end, we want to print the name of the last fruit again.
$fruit = array('apple', 'banana', 'orange'); foreach ($fruit as $f) { print "$f, "; } print $f;
Note: the purpose of this and the following examples is to demonstrate variable scoping. They are not intended to be the shortest or most efficient ways to perform the tasks.
As you probably expected, here’s the output of the above program:
apple, banana, orange, orange
This works because PHP has simple scoping rules. Within a function, any variables that get defined are available any time later in the function. Variables defined before constructs like
foreach()
andwhile()
are available inside those constructs. Variables defined inside those constructs are defined in the same scope and are available outside of those constructs, like $f above.When you first start writing programs in Ruby, you’ll probably start out by converting bits of your PHP programs over before you get into the swing.
With that in mind, let’s now try directly converting our PHP program to Ruby:
fruit = %w[apple banana orange] fruit.each { |f| print "#{f}, " } print f
If you’re wondering about the
%w
, that’s a word array (see Useful Perlisms in Ruby for this and other tricks). Otherwise, this looks very similar to the PHP version.However, you might find the results to be unexpected:
apple, banana, orange, NameError: undefined local variable or method 'f'
In Ruby, scoping is lexical. There can be many levels of scope and scope can even be manipulated. When a block is called in Ruby, it is bound to the scope of its caller. This means that within the same method, variables defined above the block are available inside the block. However, variables defined within the block are not normally available outside the block. In the example above, Ruby raised a
NameError
becausef
was only defined within the block, not above it.If you really needed to do the example in Ruby, you could define a variable above the block to store the last value through the iteration.
fruit = %w[apple banana orange] last_fruit = nil fruit.each do |f| print "#{f}, " last_fruit = f end print last_fruit
Since the
last_fruit
variable is defined above the block, it is available both inside and below the block. The program now works as you might expect.While that helps us begin to understand Ruby’s scoping and gets the job done, a much simpler and more idiomatic Ruby solution for this particular problem would be this:
fruit = %w[apple banana orange] fruit.each { |f| print "#{f}, " } print fruit.last
The
Array#last
method is the equivalent of PHP’send()
. By just using it instead, our code is both more concise and readable.