Ruby元编程读书笔记七
Back to the Basics
def a_method(a, b)
a + yield(a, b)
end
a_method(1, 2) {|x, y| (x + y) * 3} #=> 10
只有在调用一个方法时才可以定义一个块,块会被直接传递给这个方法,然后该方法可以用 yield 关键字回调这个块。
def a_method
return yield if block_given?
'no block'
end
a_method #=> "no block"
a_method { "here's a block!" } #=> "here's a block!"
在一个方法中,可以向 Ruby 询问当前的方法调用是否包含块。这可以通过 Kernel#block_given?() 方法来做到。
module Kernel
def using(resource)
begin
yield
ensure
resource.dispose
end
end
end
我们不能定义一个关键字,但是可以通过 内核方法 来伪造一个。 Kernel#using() 方法以待管理的资源作为参数,它还要接受一个块,在那里执行代码。无论块中的代码是否正常执行完成, ensure 语句都会调用 resource 的 dispose() 方法来释放它。
Closures
块不仅仅是一段浮动的代码,当代码运行时,它需要一个执行环境:局部变量、实例变量、self……这些东西是绑定在对象上的名字,简称为 绑定。块的要点在于它是完整的,它既包含代码,也包含一组绑定。
- 当定义一个块时,它会获取当时环境中的绑定,并且当把它传给一个方法时,它会带着这些绑定一起进入该方法:
def my_method x = "Goodbye" yield("cruel") end x = "Hello" my_method {|y| "#{x}, #{y} world "} #=> "Hello, cruel world"虽然在方法中定义了一个变量 x,块看到的 x 也是在块定义时绑定的 x,但是方法中的 x 对这个块来说是不可见的。基于这样的特性,计算机科学家喜欢把块称为 闭包。对我们而言,这意味着一个块可以获取局部绑定,并一直带着它们。
Scope
下面的例子演示了在程序运行时作用域是怎样切换的,它会用 Kernel#local_variables() 方法来跟踪绑定的名字:
v1 = 1
class MyClass
v2 = 2
local_variables #=> [:v2]
def my_method
v3 = 3
local_variables
end
local_variables #=> [:v2]
end
obj = MyClass.new
obj.my_method #=> [:v3]
obj.my_method #=> [:v3] (这个 v3 跟上面那个 v3 无关)
local_variables #=> [:v1, :obj]
在一些语言中,比如 Java 和 C#,有“内部作用域”的概念。在内部作用域中可以看到“外部作用域”中的变量。但 Ruby 中没有这种嵌套式的作用域,它的作用域是截然分开的:一旦进入一个新的作用域,原先的绑定就会替换为一组新的绑定。这意味着在程序进入 MyClass 后, v1 便超出作用域范围,从而就不可见了。
在定义 MyClass 的作用域中,程序定义了 v2 及一个方法。因为方法中的代码还没有被执行,所以直到类定义结束前,程序不会再打开一个新的作用域。在方法定义完成后,用 class 关键字打开的作用域会永远关闭,同时程序回到顶级作用域。
当程序第一次进入 my_method() 方法时,它会打开一个新的作用域并定义一个局部变量 v3,接着程序退出这个方法,回到顶级作用域。当程序第二次调用 my_method() 方法时,它会打开另外一个新的作用域,并且定义另外一个新的 v3 变量。
无论何时,只要程序切换了作用域,一些绑定就会被全新的绑定所取代。当然,并不是对所有的绑定都如此。如果一个对象调用同一个对象中的另外一个方法,那么实例变量在调用过程中始终存在于作用域中。尽管如此,绑定在切换作用域时往往会掉出作用域。尤其是在切换作用域时,局部变量总是会掉出的。