可调用对象:
可调用对象是一个对象,可以给该对象发送call消息,让它执行定义在其中(通常是在一个代码块中)的一些代码。Ruby中主要的可调用对象是
方法、Proc对象和lambda。
Proc对象
用一个代码块来实例化Proc类,可以产生一个Proc对象。
pr = Proc.new {puts "Inside a Proc's block"}
这里的代码块是不是会马上执行,只是作为Proc对象的一个定义体保存了下来。如果要执行它,就要发送call消息给这个对象。
pr.call #Inside a Proc's block
作为闭包的Proc对象
首先我们应该清楚:方法定义体中的局部变量和方法调用作用域中的局部变量是两回事,它们毫不相干。
Proc对象的作用域有所不同。Proc.new调用发生时作用域中的局部变量仍然在作用域中。也就是说,不论何时何地调用该对象,那些变量始终是在Proc对象的作用域中。
def call_some_proc(pr)
a = "irrelevant 'a' in method scope"
puts a
pr.call
end
a = "'a' to be used in Proc block"
pr = Proc.new{puts a}
pr.call
call_some_proc(pr)
输出结果:
'a' to be used in Proc block
irrelevant 'a' in method scope
'a' to be used in Proc block
来分析一下这个代码。先看在外围作用域中也有一个变量a,还建立了一个过程对象。接着调用这个过程对象,这时就会输出结果" 'a' to be used in Proc block ",最后是一个方法调用,参数是一个Proc对象pr.现在进入方法call_some_pro中,方法体中有个变理a,再输出a的值,这时就会输出" irrelevant 'a' in method scope ",最后是一个pr.call的调用,由于pr里保存了在外围作用域中的a,所以这里输出的结果仍是外围a的值" 'a' to be used in Proc block "。
像这样带着产生它的上下文信息的一段代码,称为闭包。
Proc对象的参数
Proc创建对象时,代码块中也可以提供接收参数。
>> pr = Proc.new { |x| puts "Call with argument #{x}" }
>> pr.call(100) #Call with argument 100
Proc对象处理它们的参数很微妙,如果给它提供的参数多于定义的个数,那么将得到一个警告。如果没有提供参数,那么该参数变量会被初始为nil,同样也会有一个警告。
如果Proc对象接受多个参数,那么调用它时所提供的参数被赋值给参数列表中的变量。多余的参数被忽略。
也可以使用星号操作符(*)将所有的参数合并为一个参数(数组):
pr = Proc.new{|*x| p x}
pr.call
pr.call(1)
pr.call(1,2)
输出:
[]
[1]
[1, 2]
如果接受多个参数,带星号的参数要放在最后,合并剩余的参数。
pr = Proc.new { |x, *y| p x, y}
pr.call(1, 2, 3)
输出:
1
[2, 3]
用lambda生成匿名函数
lambda用来生成匿名函数,需要做的就是给lambda提供一个代码块,生成的对象其实也是Proc对象,同样可以给它发送call消息。
>> lam = lambda { puts "A lambda" }
>> lam.call
=> A lambda
lambda生成的对象和所有Proc对象一样都是闭包。但是,lambda生成的Proc对象和Proc.new生成的Proc对象之间是差别的。
lambda要比Proc.new严格:
l_proc = lambda {|a, b| puts "a + b = #{a+b}"}
n_proc = lambda {|a, b| puts "a + b = #{a+b}"}
n_proc.call(1, 2, 3)
-> a + b = 3
l_proc.call(1, 2, 3)
-> !ArgumentError (wrong number of arguments (3 for 2))
lambda生成的Proc对象,在使用call调用时,如果参数不对会产生ArgumentError异常。而Proc.new生的Proc对象就不会。
还有一个区别是它们返回值的方式不一样。Proc.new生成的Proc对象是从代码块返回的。
def proc_new
new_proc = Proc.new { return "I got here..." }
new_proc.call
return "...but not here."
end
def lambda_proc
new_proc = lambda { return "You get here..." }
new_proc.call
return "And I got here!"
end
puts proc_new
-> I got here...
puts lambda_proc
-> And I got here!
再论代码块
Ruby中没有Block类或者Block对象,代码块仅在语法意义上存在。代码块是出现在方法调用之后、由大括号(或者do...end)包围起来、等待着被使用的一些代码。
在方法中可以将代码专转换成Proc对象。这可以通过给方法参数加上&来实现,并且这个加上&的变量是在参数的最后一项:
def grab_block(&block)
block.call
end
grab_block { puts "This block will end up in the variable 'block'" }
同样的,使用 & 符号也可以反过来将Proc对象或lambda转换为一个代码块。
lam = lambda { puts "This lambda will serve as a code block" }
grab_block &lam
下面是另一种写法:
grab_block &lambda { puts "This lambda will serve as a code block" }
作为对象的方法
最经常用的是方法,一般使用是间接进行的:给对象发送一条消息,然后对象执行相应的方法。其实方法也是对象,同样可以把它做为对象来处理。
通过method方法,并以方法名作为参数(以字符串或符号的形式),就可以得到方法对象。
class C
def talk
puts "Method-grabbing test! self is #{self}."
end
end
c = C.new
meth = c.method(:talk)
现在meth就是一个方法对象,而是一个绑定的方法对象,这里是绑定在对象c上的。通过给meth发送"call"消息运行。
meth.call
输出结果:
Method-grabbing test! self is #<C:0x35666>.
也可以将方法和它所绑定的对象解绑定(unbind),然后将它绑定(bind)到另一个对象上。只要这个对象和原来的对象��同一个类的实例(或者是子类的实例):
class D < C
end
d = D.new
unbound = meth.unbind
unbound.bind(d).call
输出结果:
Method-grabbing test! self is #<D:0x32d7bc>.
如果不想通过对已绑定的方法使用unbind来得到方法对象,而是直接得到解绑定的方法对象,则可以用instance_method方法,从类来得到。
unbound = C.instance_method(:talk)
这样unbound就是一个解绑定对象,要使用unbound的话,就把它绑定到C的对象上就可以了。
>> c = C.new
>> unbound.bind(c).call
绑定和解绑定的使用相对来说要少很多,能够读懂就够了。在某些场合,对于“怎么做”这个问题的最好答案是,“使用解绑定的方法”。
class A
def a_method
puts "Definition in class A"
end
end
class B < A
def a_method
puts "Definition in class B(subclass of A)"
end
end
class C < B
end
然后获得一个C的实例:
c = C.new
有没有办法叫最底层的实例(这里的c),接收到A类中的消息"a_method"呢?
很显然,默认情况下是不行的,c就会执行它在方法查找路径上第一个可以匹配的方法:
c.a_method
输出结果是:
Definition in class B(subclass of A)
但是可以通过一个解绑定和一个绑定操作解决
class C
def call_original
A.instance_method(:a_method).bind(self).call
end
end
现在直接在c上调用call_original就可以了。
如果想要熟练的掌握Ruby的动态特性,就需要理解该技术,但尽量不要使用。
method对象也是可调对象,并且,可以解开与它们的实例对象之间的绑定。