Ruby Block Scope

  • published on February 18th, 2008

    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.

    PHP

    $fruit = array('apple', 'banana', 'orange');
    foreach ($fruit as $f) { print "$f, "; }
     
    print $f;

    The purpose of this and the following examples is to demonstrate variable scoping. They are not intended to present the shortest or most efficient ways to perform specific 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() and while() 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:

    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 because f 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.

    Ruby

    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:

    Ruby

    fruit = %w[apple banana orange]
    fruit.each { |f| print "#{f}, " }
     
    print fruit.last

    The Array#last method is the equivalent of PHP’s end(). By just using it instead, our code is both more concise and readable.

5 comments

  • comment by Rafal Piekarski 17 Apr 08

    But I’ve found some other solution for this:

    fruit = %w[apple banana orange]
    last_fruit = nil

    fruit.each do |f|
    print “#{f}, ”
    last_fruit = f
    end

    print last_fruit

    which would be near php method:

    fruits = %w[apple banana orange]
    fruit = nil

    fruits.each do |fruit|
    print “#{fruit}, ”
    end

    print fruit

    It’s nicer than method with “last_fruit”. But it’s only a hint.

  • comment by Chris 7 May 09

    fruit = %w[apple banana orange]
    string = fruit.join(‘, ‘)
    puts “#{string}, #{fruit.last}”

  • comment by The MIM 16 Sep 09

    minimal version:
    f = nil
    ['apple', 'bannana', 'orange'].each {|f| print “#{f}, “}; puts f

  • comment by John 11 Apr 11

    PHP minimal version:
    echo implode(“,”, $fruits = array(‘apple’,'banana’,'orange’)); echo end($fruits);

  • comment by Ochronus 5 May 11

    I’ve written a tutorial/review on ruby blocks and closures with code examples, be sure to check it out: ruby blocks and closures

Post a comment


We welcome your participation but please note we reserve the right to remove any comments that we think are not relevant or do not contribute to the discussion.