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;

    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.

3 comments

  • comment by tim 20 Feb 08

    The sentence “In Ruby, scoping is more dynamic” scares me.

    “Dynamic scoping” has a very specific meaning. PHP appears to use dynamic scoping; Ruby uses lexical scoping, not dynamic scoping.

    I’m not sure what you mean by “more dynamic”. If you mean the dictionary definition (roughly, “energetic”), I’m not sure how that applies. If you mean the computer science definition, it sounds exactly backwards.

  • comment by Mike 20 Feb 08

    Thanks for catching the typo.

  • 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.

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.