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