SapphireSteel Software

 

  -  
     
     
     
  -  
     
     
     
  -  
     
     
     
  -  
     
     
     
  -  
     
     
     
     

 

  rss
RSS (SITE)
 
  rss
RSS (BLOG ONLY)
 
   
 
 
     

 

Section :: Blog
- Format For Printing...

Ruby Blocks, Parameters and ’for’ Loops

by Huw Collingbourne
Some twisty little corners of scoping...
Wednesday 29 August 2007.
 
There are some odd gnarly little corners of Ruby which those of us who spend our waking lives writing Ruby lexers, parsers, debuggers and IntelliSense systems are rather keenly aware of.

However, a knowledge of these quirks will be useful even if you aren’t writing an IDE! I thought it might be interesting, therefore, to explore some of the foggy little backwaters of Ruby from time to time and point out a few of things that may catch you out if you aren’t prepared for them.

First, block variables...

It’s well known that Matz, the creator of Ruby, has acknowledged that the scoping of variables within blocks exhibits what he has called ‘regrettable behaviour’. In particular, block parameters – the local variables declared between upright bars – are not scoped tightly within the block itself. Since a block is, in principle, a kind of ‘nameless method’, you would expect its block parameters to behave like the arguments to a regular method.

Let’s take an example, here’s a version in which each integer in an array is passed to a method named (inventively) aMethod:

x = 100
def aMethod( x )
  puts( x )
end

for i in [1,2,3] do
  aMethod( i )
end
print("x is now: ",  x )

The argument to aMethod is named x. So x is initialized with the values 1, 2 and 3. Outside this method is a local variable, which is also named x. This has the value 100. When we run this program, there are no surprises. As you would surely expect, the argument x to aMethod is local to the scope of that method and has no effect on the local variable, x. This is the output of the program...

1
2
3
x is now: 100

Now let’s rewrite this using a block instead of a method:

x = 100
[1,2,3].each {
  |x|
  puts( x )
}
print("x is now: ",  x )

Here the each method passes each value from the array, [1,2,3] into the block causing the block parameter, x to be initialized with the values, 1, 2 and 3, just as the method parameter, x, was initialized with those values in the last example. But now look at the output:

1
2
3
x is now: 3

The local variable, x has been initialized by the block parameter, x! This is normal behaviour in Ruby 1.8.x. If there happens to be local variable outside the block with the same name as a local variable (or block parameter) inside the block, its value will be affected by any assignment to the variable inside the block. As I said, this quirk is pretty well known among Ruby programmers, it has been highlighted as a flaw by Matz and it will be changed in future versions - from 1.9 onwards, I believe - of Ruby.

See:
- http://eigenclass.org/hiki.rb?Changes+in+Ruby+1.9
- http://rubygarden.org/ruby/page/show/Rite.

But there is another little foible of blocks and scoping which is, perhaps, not quite so well known. It concerns for loops.

Now, for a long time, I laboured under the illusion that a for loop provided an alternative syntax to the each method but that its behaviour was, to all intents and purposes, identical. I suspect this misapprehension resulted from my perusal of the online Ruby manual in my early days of Ruby programming. This is now quite an old document but it’s still online and is still frequently referenced by other writers, so I thought it safe to take its description of the for loop literally. This is what it says:

Syntax:
        for lhs... in expr [do]
          expr..
        end

Executes body for each element in the result of expression. for is the syntax sugar for:
        (expr).each `{' `|' lhs..`|' expr.. `}'

See:
- The Online Ruby Manual: http://docs.huihoo.com/ruby/ruby-man-1.4/syntax.html#for

So, a for loop, I assumed, was nothing more nor less than ‘syntax sugar’ for an each iterator. That led me to believe that I could use for loops and each iterators indiscriminately in the sure and certain knowledge that they would have an identical effect in my code.

Not so!

The big difference between for and each is their effects on local variables. Look at this method...

def foo
  a = 100
  [1,2,3].each do |b|
     c = b
     a = b
     print("a=#{a}, b=#{b}, c=#{c}\n")
  end
  print("Outside block: a=#{a}\n")
end

Here, neither b nor c are declared outside of the block so I am unable to print their values after the end block delimiter. The local variable, a is declared outside the block, however, and this is initialized with the value (3) assigned inside the block.

Now, look at the method rewritten using for:

def foo2
  a = 100
  for b in [1,2,3] do
     c = b
     a = b
     print("a=#{a}, b=#{b}, c=#{c}\n")
  end
  print("Outside block: a=#{a}, b=#{b}, c=#{b}\n")       
end

Once again, a is declared outside the block and it takes on the value (3) assigned to a inside the block. And once again, b and c are declared inside the block (at least, c is created inside the block, while b should, if this is truly just ‘syntax sugar’ for each, take on the role of the ‘block parameter’, b, from the previous example).

But this time, all three variables, a, b and c are accessible from outside the block and their values can be printed after the block’s end delimiter.

This is the kind of subtle, non-obvious behaviour that can cause headaches to those of us writing Ruby IDEs and could equally cause all kinds of weird side-effects (unless you take appropriate defensive measures such as, for example, making sure your local variable names are unique) in your own Ruby programs.

Here’s the code if you want to try this out for yourself...

AddThis Social Bookmark Button

 

© 2008 SapphireSteel Software. All rights reserved