Ross Bamford
1/4/2006 12:58:00 AM
On Wed, 04 Jan 2006 00:46:16 -0000, Daniel Schüle
<uval@rz.uni-karlsruhe.de> wrote:
> 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
> => 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
> => 222
> irb(main):552:0> cba {|p|}
> 222
> 222
> => 222
> irb(main):553:0> cba {|p| p = 333}
> 222
> 222
> => 222
> irb(main):554:0> cba {|p| puts p; p = 333}
> 222
> 222
> 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]
> => [1]
> irb(main):565:0> xxx {|x| }
> [1]
> [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,
--
Ross Bamford - rosco@roscopeco.remove.co.uk