Robert Klemme
10/2/2007 2:30:00 PM
2007/10/2, Federico Brubacher <fbrubacher@gmail.com>:
>
> > That's one way to do it. Actually only a smaller change is needed
> > because the root cause in this case is the scoping rule of the
> > iteration variable in a "for" loop which is different than when
> > iterating with a block as this example demonstrates:
> >
>
> CORRECT :
>
> > names.each do |name|
> > define_method("m1_#{name}") do
> > puts "1_#{name}"
> > end
> > end
>
> INCORRECT !
>
>
> > for name in names do
> > define_method("m2_#{name}") do
> > puts "2_#{name}"
> > end
> > end
>
>
> Wow thanks Robert that was of help ...
>
> So if i get this straight : what happens is that in the execution time
> as the for iterated over all the array until the end the variable that
> would be the one that gets printed out. If we use each to iterate over a
> block the scope of the name variable is saved and stays the same (it
> closes on itself) thus we get the correct name variable everytime.
>
> Am i right !?
Yep. With #each you get multiple bindings that contain the same name
because the block opens a new scope. With for you just get one
binding that contains the variable and so all created methods share
that same value because they fetch it from the binding. The concept
is known as closure. That's the reason why this works:
$ ruby/counter.rb
c1:1
c1:2
c1:3
c2:101
c2:102
c2:103
c2:104
c2:105
RKlemme@padrklemme1 ~
$ cat ruby/counter.rb
#!ruby
def create_counter(initial = 0)
lambda { initial += 1 }
end
c1 = create_counter
c2 = create_counter 100
3.times { print "c1:", c1.call, "\n" }
5.times { print "c2:", c2.call, "\n" }
The lambdas carry around the binding to the scope of the method
create_counter when it was invoked. As you can see, there were two
invocations and so you get two independent counters. The example is
similar to the #each version. As you can see in this example, both
closures share the same environment and thus share the counter value:
RKlemme@padrklemme1 ~
$ ruby/counter-2.rb
c1:1
c1:2
c1:3
c2:5
c2:7
c2:9
c2:11
c2:13
c1:14
c1:15
c1:16
RKlemme@padrklemme1 ~
$ cat ruby/counter-2.rb
#!ruby
def create_counter(initial = 0)
return lambda { initial += 1 }, lambda { initial += 2 }
end
c1, c2 = create_counter
3.times { print "c1:", c1.call, "\n" }
5.times { print "c2:", c2.call, "\n" }
3.times { print "c1:", c1.call, "\n" }
Kind regards
robert