这里:
return Proc.new { n = n + 1 }
实际上,返回一个 proc 对象,该对象有一个与之关联的块。Ruby 创建了一个带有块的绑定!所以执行上下文被存储起来供以后使用,因此我们可以增加 n。让我进一步解释 Ruby 闭包,以便您有更广泛的想法。
首先,我们需要澄清技术术语“绑定”。在 Ruby 中,绑定对象将执行上下文封装在程序的某个特定范围内,并保留此上下文以供将来在程序中使用。此执行上下文包括传递给方法的参数和方法中定义的任何局部变量、任何关联的块、返回堆栈和 self 的值。举个例子:
class SomeClass
def initialize
@ivar = 'instance variable'
end
def m(param)
lvar = 'local variable'
binding
end
end
b = SomeClass.new.m(100) { 'block executed' }
=> #<Binding:0x007fb354b7aca0>
eval "puts param", b
=> 100
eval "puts lvar", b
=> local variable
eval "puts yield", b
=> block executed
eval "puts self", b
=> #<SomeClass:0x007fb354ad82e8>
eval "puts @ivar", b
instance variable
最后一条语句可能看起来有点棘手,但事实并非如此。记住绑定保存执行上下文以供以后使用。所以当我们调用yield时,它调用yield就好像它仍然在那个执行上下文中一样,因此它调用了块。
有趣的是,您甚至可以在闭包中重新分配局部变量的值:
eval "lvar = 'changed in eval'", b
eval "puts lvar", b
=> changed in eval
现在这一切都很可爱,但没那么有用。绑定非常有用,因为它与块有关。Ruby 将绑定对象与块相关联。因此,当您创建 proc 或 lambda 时,生成的 Proc 对象不仅包含可执行块,还包含块使用的所有变量的绑定。
您已经知道块可以使用在块外定义的局部变量和方法参数。例如,在以下代码中,与 collect 迭代器关联的块使用方法参数 n:
# multiply each element of the data array by n
def multiply(data, n)
data.collect {|x| x*n }
end
puts multiply([1,2,3], 2) # Prints 2,4,6
更有趣的是,如果将块转换为 proc 或 lambda,即使在它作为参数的方法返回之后,它也可以访问 n。那是因为有一个绑定与 lambda 或 proc 对象的块相关联!以下代码演示:
# Return a lambda that retains or "closes over" the argument n
def multiplier(n)
lambda {|data| data.collect{|x| x*n } }
end
doubler = multiplier(2) # Get a lambda that knows how to double
puts doubler.call([1,2,3]) # Prints 2,4,6
multiplier 方法返回一个 lambda。因为这个 lambda 是在定义它的范围之外使用的,所以我们称它为闭包;它封装或“关闭”(或仅保留)方法参数 n 的绑定。
重要的是要理解闭包不仅仅保留它所引用的变量的值——它保留了实际的变量并延长了它们的生命周期。另一种说法是,在创建 lambda 或 proc 时,lambda 或 proc 中使用的变量不是静态绑定的。相反,绑定是动态的,并且在执行 lambda 或 proc 时会查找变量的值。