block_closure.rb

    …然后它就能够将该变量“携带”到不同的作用域内。例如,这里将块传递给 aMethod。当在该方法内部调用 ablock 时,它运行代码 puts(x)。这里显示,”hello world” 而不是 “goodbye”…

    1. def aMethod( aBlockArg )
    2. x = "goodbye"
    3. aBlockArg.call #<= displays "hello world"
    4. end

    在这个特定的例子中,这种行为似乎对好奇心没有太大吸引力。实际上,可以更具创造性地使用块/闭包。

    例如,你可以在方法内创建一个块并将该块返回给调用代码,而不是创建一个块并将其发送到方法。如果创建块的方法碰巧接收参数,则可以使用该参数初始化块。

    这为我们提供了一种从同一“块模板”(block template)创建多个块的简单方法,每个块的实例都使用不同的数据进行初始化。例如,在这里我创建了两个块,分配给变量 salesTaxvat,每个块根据不同的值(0.10)和(0.175)计算结果:

    block_closure2.rb
    1. def calcTax( taxRate )
    2. return lambda{
    3. |subtotal|
    4. subtotal * taxRate
    5. }
    6. end
    7. salesTax = calcTax( 0.10 )
    8. vat = calcTax( 0.175 )
    9. print( "Tax due on book = ")
    10. print( salesTax.call( 10 ) ) #<= prints: 1.0
    11. print( "\nVat due on DVD = ")
    12. print( vat.call( 10 ) ) #<= prints: 1.75

    块的一个不太明显的特性是它们使用变量的方式。如果一个块可能真的被视为匿名函数或方法,那么从逻辑上讲,它应该能够:1)包含它自己的局部变量;2)能够访问该块所属的对象的实例变量。

    我们先来看实例变量(instance variables)。加载 closures1.rb 程序。这提供了块等同于闭包的另一个例子 - 通过捕获创建它的作用域中的局部变量的值。这里我使用 lambda 方法创建了块:

    closures1.rb
    1. aClos = lambda{
    2. @hello << " yikes!"
    3. }

    这个块将一个字符串 “yikes!” 附加到一个实例变量 @hello。请注意,在这个过程中,之前没有为 @hello 分配任何值。

    但是,我创建了一个单独的方法 aFunc,它为一个名为 @hello 的变量赋值:

    1. def aFunc( aClosure )
    2. @hello = "hello world"
    3. aClosure.call
    1. aFunc(aClos) #<= @hello = “hello world yikes!”
    2. aClos.call #<= @hello = “hello world yikes! yikes!”
    3. aClos.call #<= @hello = “hello world yikes! yikes! yikes!”
    4. aClos.call # ...and so on
    5. aClos.call

    如果你认为这并不是太令人惊讶。毕竟,@hello 是一个实例变量,因此它存在于一个对象的作用域内。当我们运行 Ruby 程序时,会自动创建一个名为 main 的对象。所以我们应该期望在该对象(我们的程序)中创建的任何实例变量可用于其中的所有内容。

    现在出现的问题是:如果要将块发送到某个其它对象的方法会发生什么?如果该对象有自己的实例变量 ,那么该块会使用哪个变量 - 来自创建块的作用域内的 @hello,还是来自调用该块的对象作用域内的 @hello?让我们尝试一下。我们将使用与以前相同的块,除了这次它将显示有关块所属对象和 @hello 值的一些信息:

    现在从新类(X)创建一个新对象,并为它提供一个接收我们的块 b 的方法,并调用该块:

    1. class X
    2. def y( b )
    3. @hello = "I say, I say, I say!!!"
    4. puts( " [In X.y]" )
    5. puts("in #{self} object of class #{self.class}, @hello = #{@hello}")
    6. puts( " [In X.y] when block is called..." )
    7. b.call
    8. end
    9. end
    10. x = X.new

    要测试它,只需将块 aClos 传递给 xy 方法:

    1. x.y( aClos )

    这就是显示的内容:

    1. [In X.y]
    2. in #<X:0x32a6e64> object of class X, @hello = I say, I say, I say!!!
    3. [In X.y] when block is called...
    4. in main object of class Object, @hello = hello world yikes! yikes! yikes! yikes! yikes! yikes!

    因此,很明显,块在创建它的对象(main)的作用域内执行,并保留该对象的实例变量,即使在调用块的对象的作用域内有一个具有相同名称和不同值的实例变量。

    现在让我们看看块/闭包(block/closure)如何处理局部变量(local variables)。加载 closures2.rb 程序。首先,我声明一个变量 x,它对程序本身的上下文来说是局部的:

    closures2.rb
    1. x = 3000

    第一个块/闭包称为 c1。每次我调用这个块时,它会获取块本身外部定义的 x 值(3000)并返回 x+100

    1. c1 = lambda{
    2. return x + 100
    3. }
    注意:如上所示,你可以将调用放在块中并将其传递给 Integer 的 times 方法,而不是重复调用 c1,如下所示: 2.times{ someval=c1.call(someval); puts(someval) }但是,因为它可能很难在只有一个块(例如这里的 c1 块)的情况下工作,以至于我故意避免使用比这个程序中更多本应该必要的块!

    第二个块名为 c2。这声明了一个’块参数’(block parameter),z。这也返回一个值:

    1. c2 = lambda{
    2. |z|
    3. return z + 100

    但是,这次返回值可以重复使用,因为块参数就像一个方法的传入参数 - 所以当 someval 的值在被赋值为 c2 的返回值之后被更改时,这个更改的值随后作为参数传入:

    1. someval=1000
    2. someval=c2.call(someval); puts(someval) #<= someval is now 1100

    乍一看,第三个块 c3 与第二个块 c2 几乎相同。实际上,唯一的区别是它的块参数被称为 x 而不是 z

    1. c3 = lambda{
    2. |x|
    3. return x + 100
    4. }

    块参数的名称对返回值没有影响。和以前一样,someval 首先被赋值 1100(即,它的原始值 1000,加上块中添加的 100)然后,当第二次调用块时,someval 被赋值为 1200(其先前的值 1100,加上在块内分配的 100)。

    但现在看一下局部变量 x 的值会发生什么。在该单元的顶部分配了 3000。只需给块参数指定相同的名称 x,我们就改变了局部变量 x 的值。它现在具有值 1100,即块参数 x 在调用 c3 块时最后具有的值:

    1. x = 3000
    2. c3 = lambda{
    3. |x|
    4. return x + 100
    5. }
    6. someval=1000
    7. someval=c3.call(someval); puts(someval)
    8. someval=c3.call(someval); puts(someval)
    9. puts( x ) #<= x is now 1100

    顺便提一下,即使块局部变量和块参数可以影响块外部的类似命名的局部变量,块变量本身也不会在块之外存在。你可以使用 defined? 关键字对此进行验证,以尝试显示变量的类型(如果确实已定义):

    1. print("x=[#{defined?(x)}],z=[#{defined?(z)}]")

    Ruby 的创造者 Matz,他将块内局部变量的作用域描述为“抱歉的”(regrettable)。特别是,他认为在一个块中使局部变量对包含该块的方法不可见是错误的。有关此示例,请参阅 local_var_scope.rb

    local_var_scope.rb

    这里,块参数 b 和块局部变量 c 只有在块本身内部时才可见。该块可以访问这些变量并作用于变量 afoo 方法的局部)。但是,在块之外,bc 是不可访问的,只有a 是可见的。

    1. def foo2
    2. a = 100
    3. for b in [1,2,3] do
    4. c = b
    5. a = b
    6. print("a=#{a}, b=#{b}, c=#{c}\n")
    7. end
    8. print("Outside block: a=#{a}, b=#{b}, c=#{b}\n")
    9. end

    在 Ruby 的未来版本中,在块内赋值的局部变量(与 c 一样)也将是块外部方法(例如 foo)的局部变量。形式上块参数(如 b)将是块的局部变量。