Ruby元编程读书笔记九
Scope Wrap-Up
每个 Ruby 作用域都包含一组绑定,并且不同的作用域之间被 作用域门 分隔开来: class、module 和 def。如果要让一两个绑定穿越作用域门,那么可以用方法调用来替代作用域门:用一个闭包获取当前的绑定,并把这个闭包传递给该方法。你可以使用 Class.new() 方法代替 class,使用 Module.new() 代替 module,以及使用 Module#define_method() 代替 def,这就形成了一个 扁平作用域,它是闭包中的一个基本概念。如果在一个扁平作用域中定义了多个方法,则这些方法可以用一个作用域门进行保护,并共享绑定,这种技术称为 共享作用域。
instance_eval()
Object#instance_eval() 方法可以在一个 对象的上下文 中执行一个块:
class MyClass
def initialize
@v = 1
end
end
obj = MyClass.new
obj.instance_eval do
self #=> #<MyClass:0x3340dc @v=1>
@v #=> 1
end
在运行时,该块的接收者会成为 self,因此它可以访问接收者的私有方法和实例变量,例如 @v。instance_eval() 方法甚至可以在不碰其他绑定的情况下修改 self 对象:
v = 2
obj.instance_eval { @v = v}
obj.instance_eval { @v } #=> 2
- 可以把传递给 instance_eval() 方法的块称为一个 上下文探针,因为它就像一个深入到对象中的代码片段,对其进行操作。
instance_exec()
instance_exec() 方法和instance_eval() 的功能相似,但它允许对块传入参数:
class C
def initialize
@x, @y = 1, 2
end
end
C.new.instance_exec(3) {|arg| (@x + @y) * arg } #=> 9
Clean Rooms
有时你会创建一个对象,仅仅是为了在其中执行块,这样的对象称为 洁净室。
class CleanRoom
def complex_calculation
#...
end
def do_something
#...
end
end
clean_room = CleanRoom.new
clean_room.instance_eval do
if complex_calculation > 10
do_something
end
end
洁净室仅仅是一个用来执行块的环境,它通常还会暴露若干有用的方法供块调用。
Callable Objects
从底层来看,使用块需要分为两步。第一步,将代码打包备用;第二步,调用块(通过 yield 语句)来执行代码。这种“先打包代码,以后调用”的机制并不是块的专利。在 Ruby 中,至少还有以下三种方法可以用来打包代码:
- 使用 proc。proc 基本上就是一个由块转换成的对象。
- 使用 lambda。它是 proc 的近亲。
- 使用方法。
Proc Objects
尽管 Ruby 中绝大多数东西都是对象,但是块不是。一个 Proc 就是一个转换成对象的块,可以通过把块传给 Proc.new 方法来创建一个 Proc,以后就可以用 Proc#call() 方法来执行这个由块转换而来的对象,这种技术称为 延迟执行。
inc = Proc.new {|x| x + 1}
# ...
inc.call(2) #=> 3
Ruby 还提供了两个内核方法用于把块转换为 Proc:lambda() 和 proc()。一会就能看到 lambda() 、proc() 和 Proc.new() 这三种方式之间有一些细微的差别,但在大多数情况下,你可以随便挑一个最喜欢的方式来使用:
dec = lambda {|x| x - 1}
dec.class #=> Proc
dec.call(2) #=> 1