Forum: Ruby Volatile variables in loops?

8e16f7669af5b4ecfa4f2b89f32b21b6?d=identicon&s=25 Stefan Salewski (Guest)
on 2013-11-06 00:53
(Received via mailing list)
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
6e366eb5a71be2bad7f383d42aeb4788?d=identicon&s=25 Justin Collins (Guest)
on 2013-11-06 01:24
(Received via mailing list)
On 11/05/2013 03:42 PM, Stefan Salewski wrote:
> Programming Language" but can not really remember this topic for loops
> 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
8e16f7669af5b4ecfa4f2b89f32b21b6?d=identicon&s=25 Stefan Salewski (Guest)
on 2013-11-06 01:42
(Received via mailing list)
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, 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
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (robert_k78)
on 2013-11-06 08:35
(Received via mailing list)
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. :-)

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

> 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
8e16f7669af5b4ecfa4f2b89f32b21b6?d=identicon&s=25 Stefan Salewski (Guest)
on 2013-11-06 15:39
(Received via mailing list)
On Wed, 2013-11-06 at 08:34 +0100, Robert Klemme wrote:
> 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.

Best regards

Stefan Salewski
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (robert_k78)
on 2013-11-06 15:59
(Received via mailing list)
On Wed, Nov 6, 2013 at 3:37 PM, Stefan Salewski <mail@ssalewski.de>
wrote:
>> $ ruby -e 'for i in 0..5; j=i; puts i; end; puts "--", j'
>
> 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.

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

robert
Ce43b11af679aaa142f5ba7e35e64c4b?d=identicon&s=25 Sur Max (sur)
on 2013-11-07 02:58
(Received via mailing list)
@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
6e366eb5a71be2bad7f383d42aeb4788?d=identicon&s=25 Justin Collins (Guest)
on 2013-11-07 15:10
(Received via mailing list)
On 11/06/2013 05:58 PM, Sur wrote:
> Ruby 1.9.3
> a = nil
>
> /Note that the block variables now is only |i|  and not |i; j|/
>
>
>
> regards,
> Sur
> crismon9.com <http://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
8e16f7669af5b4ecfa4f2b89f32b21b6?d=identicon&s=25 Stefan Salewski (Guest)
on 2013-11-07 15:47
(Received via mailing list)
On Thu, 2013-11-07 at 07:28 +0530, Sur wrote:
>  => 5
>
> *Note that the block variables now is only |i|  and not |i; j|*

$ 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.
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.