Block and method local variables

hello all,

consider follow code

irb(main):528:0* def abc
irb(main):529:1> ppp=111
irb(main):530:1> puts ppp
irb(main):531:1> yield
irb(main):532:1> puts ppp
irb(main):533:1> ppp
irb(main):534:1> end
=> nil
irb(main):536:0> abc {}
111
=> 111
irb(main):537:0> abc { puts ppp }
111
NameError: undefined local variable or method `ppp' for main:Object
         from (irb):537
         from (irb):537:in `abc'
         from (irb):537
irb(main):538:0>
irb(main):539:0*
irb(main):540:0* ppp = "OOO"
=> "OOO"
irb(main):541:0> abc { puts ppp }
111
OOO
111
=> 111
irb(main):542:0>

I am right assuming that all "local" variables in the method abc
are invisible in the block?

I tried a little more

irb(main):544:0* def cba
irb(main):545:1> ppp = 222
irb(main):546:1> puts ppp
irb(main):547:1> yield ppp
irb(main):548:1> puts ppp
irb(main):549:1> ppp
irb(main):550:1> end
=> nil
irb(main):551:0> cba {}
222
=> 222
irb(main):552:0> cba {|p|}
222
=> 222
irb(main):553:0> cba {|p| p = 333}
222
=> 222
irb(main):554:0> cba {|p| puts p; p = 333}
222
=> 222
irb(main):555:0>

if explicitely passed to block ppp is now visible there.
but seems to be imposible to change the "method local" variable
ppp in this case.

the following *does* work

irb(main):557:0* def xxx
irb(main):558:1> ppp = [1]
irb(main):559:1> p ppp
irb(main):560:1> yield ppp
irb(main):561:1> p ppp
irb(main):562:1> ppp
irb(main):563:1> end
=> nil
irb(main):564:0> xxx {}
[1]
=> [1]
irb(main):565:0> xxx {|x| }
[1]
=> [1]
irb(main):566:0> xxx {|x| x[0]=2 }
[1]
[2]
=> [2]
irb(main):567:0>

and I kind of understand why it works

if I change line 566 to
irb(main):566:0> xxx {|x| x=[2] }
than it would not work

the reason I came accross this is following
I was reading

"
Parameters to a block may be existing local variables; if so, the new value of the variable will be retained after the block completes. This may lead to unexpected behavior, but there is also a performance gain to be had by using variables that already exist
"
and in my understanding all variables defined in a method
are "local" (C++ background)

If not local what are they considered to be then?

Regards, Daniel

···

from :0

hello all,

consider follow code

irb(main):528:0* def abc
irb(main):529:1> ppp=111
irb(main):530:1> puts ppp
irb(main):531:1> yield
irb(main):532:1> puts ppp
irb(main):533:1> ppp
irb(main):534:1> end
=> nil
irb(main):536:0> abc {}
111
=> 111
irb(main):537:0> abc { puts ppp }
111
NameError: undefined local variable or method `ppp' for main:Object
         from (irb):537
         from (irb):537:in `abc'
         from (irb):537
         from :0
irb(main):538:0>
irb(main):539:0*
irb(main):540:0* ppp = "OOO"
=> "OOO"
irb(main):541:0> abc { puts ppp }
111
OOO
111
=> 111
irb(main):542:0>

I am right assuming that all "local" variables in the method abc
are invisible in the block?

Yes. The block captures the scope it is defined in, not the scope it's called from, i.e. it's a closure.

I tried a little more

irb(main):544:0* def cba
irb(main):545:1> ppp = 222
irb(main):546:1> puts ppp
irb(main):547:1> yield ppp
irb(main):548:1> puts ppp
irb(main):549:1> ppp
irb(main):550:1> end
=> nil
irb(main):551:0> cba {}
222
=> 222
irb(main):552:0> cba {|p|}
222
=> 222
irb(main):553:0> cba {|p| p = 333}
222
=> 222
irb(main):554:0> cba {|p| puts p; p = 333}
222
=> 222
irb(main):555:0>

if explicitely passed to block ppp is now visible there.
but seems to be imposible to change the "method local" variable
ppp in this case.

Yes. Ignoring implementation details, the block parameter 'p' is given a reference to the same object. When you then assign to 'p' that reference is replaced by a new reference to the object you assigned. Note that this is only for 'p' - 'ppp' still has the same reference it always had (to the original object).

The reality is apparently slightly more complex but I believe that for most practical purposes (including this)you can ignore that.

the following *does* work

irb(main):557:0* def xxx
irb(main):558:1> ppp = [1]
irb(main):559:1> p ppp
irb(main):560:1> yield ppp
irb(main):561:1> p ppp
irb(main):562:1> ppp
irb(main):563:1> end
=> nil
irb(main):564:0> xxx {}
[1]
=> [1]
irb(main):565:0> xxx {|x| }
[1]
=> [1]
irb(main):566:0> xxx {|x| x[0]=2 }
[1]
[2]
=> [2]
irb(main):567:0>

and I kind of understand why it works

if I change line 566 to
irb(main):566:0> xxx {|x| x=[2] }
than it would not work

Correct. The original code doesn't actually assign anything to 'x', but instead calls the = method on it, which modifies the array's content. Since both 'ppp' and 'x' reference the same Array instance, your change makes it out of the block.

In the second case, you _do_ assign to 'x', supplying a new array. 'ppp' retains it's original value, so the new array is (almost) lost.

Almost, because of course it's not _quite_ lost at that point:

  irb(main):001:0> def xxx
  irb(main):002:1> ppp = [1]
  irb(main):003:1> p ppp
  irb(main):004:1> ppp = yield ppp
  irb(main):005:1> p ppp
  irb(main):006:1> end
  => nil
  irb(main):007:0> xxx { |x| x = [2] }
  [1]
  [2]
  => nil

the reason I came accross this is following
I was reading

"
Parameters to a block may be existing local variables; if so, the new value of the variable will be retained after the block completes. This may lead to unexpected behavior, but there is also a performance gain to be had by using variables that already exist
"
and in my understanding all variables defined in a method
are "local" (C++ background)

If not local what are they considered to be then?

They are local. I think that test is referring to this:

  irb(main):017:0> def test(arg)
  irb(main):018:1> p arg
  irb(main):019:1> [1,2,3].select { |arg| arg % 2 == 0 }
  irb(main):020:1> p arg
  irb(main):021:1> end
  => nil
  irb(main):022:0> test("ten")
  "ten"
  3
  => nil

An interesting aside to this (IMHO) is this:

  irb(main):018:0> class Demo
  irb(main):019:1> def last=(arg)
  irb(main):020:2> (@last ||= ) << arg
  irb(main):021:2> end
  irb(main):022:1> def all
  irb(main):023:2> @last
  irb(main):024:2> end
  irb(main):025:1> end
  => nil

  irb(main):026:0> d = Demo.new
  => #<Demo:0xb7f49a6c>
  irb(main):027:0> ['one','two','three'].each { |d.last| }
  => ["one", "two", "three"]
  irb(main):028:0> d.last
  => ["one", "two", "three"]

Which I guess illustrates that block arguments are handled by assignment to the named variable (or method in this case), and should make more sense of the preceeding example...

Cheers,

···

On Wed, 04 Jan 2006 00:46:16 -0000, Daniel Schüle <uval@rz.uni-karlsruhe.de> wrote:

--
Ross Bamford - rosco@roscopeco.remove.co.uk

Daniel Schüle wrote:

<snip/>

if I change line 566 to
irb(main):566:0> xxx {|x| x=[2] }
than it would not work

Note, that you can use get the return value of the block from yield:

def foo
  p yield( 111 )
end

foo {|x| x + 10}

121
=> nil

the reason I came accross this is following
I was reading

"
Parameters to a block may be existing local variables; if so, the new
value of the variable will be retained after the block completes. This
may lead to unexpected behavior, but there is also a performance gain
to be had by using variables that already exist
"
and in my understanding all variables defined in a method
are "local" (C++ background)

If not local what are they considered to be then?

They are local. Stress in the setence above must be on "existing". It
means a situation like this:

def get_last(enum)
  last = nil
  enum.each {|last|}
  last
end

get_last [1,2,43,3,2,4]

=> 4

This works because "last" is defined before the block. This does not
work:

def get_last(enum)
  enum.each {|last|}
  last
end

get_last [1,2,43,3,2,4]

NameError: undefined local variable or method `last' for main:Object
        from (irb):26:in `get_last'
        from (irb):28

IOW, if the variable is defined in the surrounding scope that is the one
used. If it's defined only in the block (either as parameter or in the
body) it's visibility is limited to the block. Note that this is how most
PL do nested scoping.

/* C */
int foo() {
  int x;
  {
    /* x is visible here */
    int y = x;
  }
  /* no y visible here */
}

Note, that the scope of block parameters may change in the future. There
have been lengthy discussions about this but ATM I don't remember the
details.

Kind regards

    robert

···

from :0

Okay, I'm too tired...

They are local. I think that test is referring to this:

                                ^^^^
That should be 'text'

An interesting aside to this (IMHO) is this:

  irb(main):018:0> class Demo
  irb(main):019:1> def last=(arg)
  irb(main):020:2> (@last ||= ) << arg
  irb(main):021:2> end
  irb(main):022:1> def all
  irb(main):023:2> @last
  irb(main):024:2> end
  irb(main):025:1> end
  => nil

  irb(main):026:0> d = Demo.new
  => #<Demo:0xb7f49a6c>
  irb(main):027:0> ['one','two','three'].each { |d.last| }
  => ["one", "two", "three"]
  irb(main):028:0> d.last

                          ^^^^
And that should be:

  irb(main):028:0> d.all

  => ["one", "two", "three"]

That'll teach me to refactor in Opera...

···

On Wed, 04 Jan 2006 00:58:08 -0000, Ross Bamford <rosco@roscopeco.remove.co.uk> wrote:

--
Ross Bamford - rosco@roscopeco.remove.co.uk