Volatile variables in loops?

irb(main):009:0* 5.times{|i; j| if i == 0 then j = 1 end; print i, j,
"\n"}
01
1
2
3
4
=> 5

I read about the behaviour of blocks and variables in "The Ruby
Programming Language" but can not really remember this topic for loops
-- it really surprises me. (I found a similar question at
stackoverflow.com, but no good answer.)

So Ruby 1.9.3 forgets all local variable values for each loop iteration.
Good to know this. Was it mentioned clearly in the documentation or the
textbooks? And for what is this behaviour in loops really good? I am
really happy that it took me only 10 minutes to find that problem in my
code, I should try to keep it in my head. ( Of course the solution, as
mentioned in Matz's book, is simple a j=0 in front of the loop.)

Best regards

Stefan Salewski

This is a block, just like any other block. The code is explicitly setting j to be a block local variable, so each time the block is invoked it is a fresh variable. There is no difference between your code and this code, assuming j is not an existing variable:

5.times{|i| j = nil; if i == 0 then j = 1 end; print i, j, "\n"}

(Actually, you don't even need `j = nil`, but that's more of an accident of Ruby's implementation.)

Just like one wouldn't expect a variable local to a method to persist each time the method is called, neither does a variable local to a block. The difference is that a block is also a closure, so it can reference and manipulate variables in the scope surrounding it.

-Justin

···

On 11/05/2013 03:42 PM, Stefan Salewski wrote:

irb(main):009:0* 5.times{|i; j| if i == 0 then j = 1 end; print i, j,
"\n"}
01
1
2
3
4
=> 5

I read about the behaviour of blocks and variables in "The Ruby
Programming Language" but can not really remember this topic for loops
-- it really surprises me. (I found a similar question at
stackoverflow.com, but no good answer.)

So Ruby 1.9.3 forgets all local variable values for each loop iteration.
Good to know this. Was it mentioned clearly in the documentation or the
textbooks? And for what is this behaviour in loops really good? I am
really happy that it took me only 10 minutes to find that problem in my
code, I should try to keep it in my head. ( Of course the solution, as
mentioned in Matz's book, is simple a j=0 in front of the loop.)

Best regards

Stefan Salewski

Yes indeed -- in Ruby each loop is in first order a block, that is what
I forgot. For a block this behaviour is well documented. I have a loop
with two iterations, in the first iteration I make some expensive
calculations, which I would like to reuse in the second iteration. So I
have to define that variables before the loop, and of course I have to
write j = 0; 2.times{|i| -- not 2.times{|i; j|.

I should really try hard to remember that.

Thanks,

Stefan Salewski

···

On Tue, 2013-11-05 at 16:23 -0800, Justin Collins wrote:

This is a block, just like any other block.

Not each loop but #each loop. :slight_smile:

$ ruby -e 'for i in 0..5; j=i; puts i; end; puts "--", j'
0
1
2
3
4
5

···

On Wed, Nov 6, 2013 at 1:40 AM, Stefan Salewski <mail@ssalewski.de> wrote:

On Tue, 2013-11-05 at 16:23 -0800, Justin Collins wrote:

This is a block, just like any other block.

Yes indeed -- in Ruby each loop is in first order a block,

--
5

There is no block with a for loop.

that is what
I forgot. For a block this behaviour is well documented. I have a loop
with two iterations, in the first iteration I make some expensive
calculations, which I would like to reuse in the second iteration. So I
have to define that variables before the loop, and of course I have to
write j = 0; 2.times{|i| -- not 2.times{|i; j|.

I should really try hard to remember that.

Basically it's the same with most languages with nested scopes:
variables are visible in the smallest surrounding scope. There are
some subtleties in some languages but that rule covers many cases -
certainly for the more popular languages of today.

Depending on what you do #map or #inject might be alternatives (i.e.
if you calculate values per original value or aggregate data in a
single object). There's also #each_with_object.

Kind regards

robert

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Thanks for that important hint.
One point, which has confused me, is that blocks can be limited by {} or
a do/end pair. But for the for loop (also while and until loops I think)
the do/end keywords do not build a block.

Best regards

Stefan Salewski

···

On Wed, 2013-11-06 at 08:34 +0100, Robert Klemme wrote:

On Wed, Nov 6, 2013 at 1:40 AM, Stefan Salewski <mail@ssalewski.de> > wrote:
> On Tue, 2013-11-05 at 16:23 -0800, Justin Collins wrote:
>> This is a block, just like any other block.
>
> Yes indeed -- in Ruby each loop is in first order a block,

Not each loop but #each loop. :slight_smile:

$ ruby -e 'for i in 0..5; j=i; puts i; end; puts "--", j'
0
1
2
3
4
5
--
5

There is no block with a for loop.

Probably because there are no "do/end" keywords.

robert

···

On Wed, Nov 6, 2013 at 3:37 PM, Stefan Salewski <mail@ssalewski.de> wrote:

On Wed, 2013-11-06 at 08:34 +0100, Robert Klemme wrote:

On Wed, Nov 6, 2013 at 1:40 AM, Stefan Salewski <mail@ssalewski.de> >> wrote:
> On Tue, 2013-11-05 at 16:23 -0800, Justin Collins wrote:
>> This is a block, just like any other block.
>
> Yes indeed -- in Ruby each loop is in first order a block,

Not each loop but #each loop. :slight_smile:

$ ruby -e 'for i in 0..5; j=i; puts i; end; puts "--", j'
0
1
2
3
4
5
--
5

There is no block with a for loop.

Thanks for that important hint.
One point, which has confused me, is that blocks can be limited by {} or
a do/end pair. But for the for loop (also while and until loops I think)
the do/end keywords do not build a block.

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

@Stefan

It's nothing like that Ruby is forgetting the loop's local variables or
so...

To me, the code has some problems. We are actually initializing the "j" in
every repetition of the loop and that's why it is not printing anything for
a nil object.
What we are doing above is exactly like this:

Ruby 1.9.3

a = 1
print a # => prints 1 and returns nil
a = nil
print a # => prints blank and returns nil

Ruby 1.8.7

a = 1
print a # => prints 1 and returns nil
a = nil
print a # => prints "nil" and returns nil

So, only difference in Ruby 1.8 and 1.9.3 is that earlier it used to print
"nil" for nil objects and now it doesn't print anything for nil objects
which logically sounds better.

In your code if you'd remove the initialization of "j" in every rep of the
loop you'll get what you want:

e.g.

Ruby 1.9.3

5.times{|i| if i == 0 then j = 1 end; print i, j,"\n"}
01
11
21
31
41
=> 5

*Note that the block variables now is only |i| and not |i; j|*

regards,
Sur
crismon9.com

This is not accurate.

1.8.7 :001 > 5.times{|i| if i == 0 then j = 1 end; print i, j,"\n"}
01
1nil
2nil
3nil
4nil
  => 5

1.9.3p448 :001 > 5.times{|i| if i == 0 then j = 1 end; print i, j,"\n"}
01
1
2
3
4
  => 5

-Justin

···

On 11/06/2013 05:58 PM, Sur wrote:

@Stefan

It's nothing like that Ruby is forgetting the loop's local variables or
so...

To me, the code has some problems. We are actually initializing the "j"
in every repetition of the loop and that's why it is not printing
anything for a nil object.
What we are doing above is exactly like this:

Ruby 1.9.3

a = 1
print a # => prints 1 and returns nil
a = nil
print a # => prints blank and returns nil

Ruby 1.8.7

a = 1
print a # => prints 1 and returns nil
a = nil
print a # => prints "nil" and returns nil

So, only difference in Ruby 1.8 and 1.9.3 is that earlier it used to
print "nil" for nil objects and now it doesn't print anything for nil
objects which logically sounds better.

In your code if you'd remove the initialization of "j" in every rep of
the loop you'll get what you want:

e.g.

Ruby 1.9.3

5.times{|i| if i == 0 then j = 1 end; print i, j,"\n"}
01
11
21
31
41
  => 5

/Note that the block variables now is only |i| and not |i; j|/

regards,
Sur
crismon9.com <http://crismon9.com>

$ ruby -v
ruby 1.9.3p448 (2013-06-27 revision 41675) [x86_64-linux]
stefan@AMD64X2 ~ $ irb
irb(main):001:0> 5.times{|i| if i == 0 then j = 1 end; print i, j,"\n"}
01
1
2
3
4
=> 5

I will get your result when j is declared prior outside of the
block/loop -- I was aware of that.

irb(main):003:0> j = nil; 5.times{|i| if i == 0 then j = 1 end; print i,
j,"\n"}
01
11
21
31
41
=> 5

I have to remember that .each and .times use blocks, so I have to
consider block scoping rules. But for, while and until loops do not need
blocks. Thanks, I think I understand this now.

···

On Thu, 2013-11-07 at 07:28 +0530, Sur wrote:

e.g.

Ruby 1.9.3

5.times{|i| if i == 0 then j = 1 end; print i, j,"\n"}
01
11
21
31
41
=> 5

*Note that the block variables now is only |i| and not |i; j|*