I've been having 开发者_StackOverflowa problem with a pesky little function in a class in a library that I did not create (and thus, cannot edit). Here's a simple class with the annoying behavior isolated:
class Foo # This is a class I cannot
def setmyproc(&code) # safely edit.
@prc = Proc.new # Due to it being in a
end # frustratingly complex
def callmyproc() # hierarchy, I don't
@prc.call # even know how to reopen
end # it. Consider this class
end # set in stone.
I run into a problem when I try to iterate and generate an array of these objects. I expected a different value to be substituted for i
into the Proc with each object, but what happens instead is that the variable i
is shared between them.
$bar = []
for i in (0..15)
$bar[i] = Foo.new
$bar[i].setmyproc { puts i }
end
$bar[3].callmyproc # expected to print 3
$bar[6].callmyproc # expected to print 6
Output
15 15
What can I do inside the loop to preserve separate values of i
for each object?
Use this:
$bar = []
(0..15).each do |i|
$bar[i] = Foo.new
$bar[i].setmyproc { puts i }
end
$bar[3].callmyproc # prints 3
$bar[6].callmyproc # prints 6
If you really need to make the change inside of the loop, use this (ruby 1.9 only):
$bar = []
for i in (0..15)
->(x) do
$bar[x] = Foo.new
$bar[x].setmyproc { puts x }
end.(i)
end
$bar[3].callmyproc # prints 3
$bar[6].callmyproc # prints 6
The block that gets passed into each Foo in the $bar array is bound to the same variable i
. Anytime you send callmyproc
the current value of i
in the original scope is used.
$bar[3].callmyproc
=> 15
$bar[6].callmyproc
=> 15
i = 42
$bar[3].callmyproc
=> 42
$bar[6].callmyproc
=> 42
You need to send a different object into each proc:
0.upto(15) do |i|
$bar[i] = Foo.new
$bar[i].setmyproc { i.to_i }
end
$bar[3].callmyproc
=> 3
$bar[6].callmyproc
=> 6
Ok, so first of all, welcome to closures :)
A closure is a piece of code you can pass around like a variable, that is the easy part. The other side is that a closure maintains the scope that it was called in.
What is actually happening is that as you store your procs, each one is taking along a reference to n. even though you go out of the scope of the for loop, that reference to n still sticks around, and every time you execute your procs, they are printing the final value of n. The problem here is that each iteration is not in its own scope.
What Adrian suggested to do is swap your for loop for a range.each block. The difference is that each iteration does have its own scope, and that is what is bound to the proc
$bar = []
(0..15).each do |i|
#each i in here is local for this block
$bar[i] = Foo.new
$bar[i].setmyproc { puts i }
end
This really is not a simple thing to wrap your head around, but its one of those things that will keep tripping you up until you really get it. I probably did a terrible job of explaining it, if it doesn't gel I would spend a bit of time googling how closures work with scope.
精彩评论