Ruby 里面主要的可调用的对象是 Proc 对象,Lambdas,方法对象。Proc 是独立的代码序列,你可以创建,存储,可以作为方法的参数,你愿意的话,也可以使用 call 方法执行它。Lambdas 跟 Proc 对象很像,Lambda 其实就是 Proc 对象,不过稍有不同。
Proc 对象
用 Proc.new 创建一个 Proc 实例:
pr = Proc.new { puts "inside a proc's block" }
上面的代码块就是 Proc 的主体,调用 Proc 的时候会执行代码块里的东西:
pr.call
结果是:
inside a proc's block
给 proc 方法一个代码块,它会给你返回一个 Proc 对象。
proc { puts "hi!" }
Procs 与 Blocks
不是所有的代码块都跟 Proc 一样。
[1,2,3].each {|x| puts x * 10 }
上面用了个代码块,但是并没有创建一个 proc。
一个方法可以捕获一个代码块:
def call_a_proc(&block) block.call end call_a_proc { puts "I'm the block ... or Proc ... or someting." }
输出的是:
I'm the block ... or Proc ... or someting.
用 proc 也行:
p = Proc.new {|x| puts x.upcase} %w{ Matt Damon }.each(&p)
输入的是:
MATT DAMON
语法(blocks)与对象(procs)
Ruby 的代码块不是一个对象。
[1,2,3].each {|x| puts x * 10}
接收者是一个对象,代码块不是。代码块是调用方法语法的一部分。把代码块想成是一个参数列表。
puts c2f(100)
上面调用的方法里,参数是对象,但整体的参数列表并不是对象:(100)。没有 ArgumentList 类,也没有 CodeBlock 类。
block 与 proc 转换
def capture_block(&block) block.call end capture_block { puts "inside the block" }
上面隐式调用了 Proc.new,使用同样的区块。
解释:
1 调用 capture_block 方法,给它提供一个代码块:
capture_block { puts "inside the block" }
2 使用同样的区块创建了一个 Proc 对象:
Proc.new { puts "inside the block" }
3 block 参数绑定了 Proc 对象。
def capture_block(&block) puts "got block as proc" block.call end
Proc 作为代码块
p = Proc.new { puts "this proc argument will serve as a code block" } capture_block(&p)
输出的是:
this proc argument will serve as a code block
用 & 符号标记的 proc 会作为一个代码块,所以你就不能再给同一个方法提供一个代码块了。执行下面的代码会报错:
capture_block(&p) { puts "this is the explicit block" }
提示:“both block arg and actual block given”。Ruby 不能判断你到底想用 proc 还是 block 作为代码块。你只能选择一个。
&p
里的 & 是 to_proc
方法的包装。在 Proc 对象上调用 to_proc
会返回 Proc 对象本身。
你仍然需要 &,如果你想做:
capture_block(p)
或者:
capture_block(p.to_proc)
这样做你传递的只是一般的参数。没有让 proc 参数作为代码块。也就是说在 capture_block(&p)
里面,这个 & 有两个意思,它会触发在 p
上执行 to_proc
方法,还会让 Ruby 把执行了 to_proc
返回的 proc 对象当成是一个代码块。
Symbol#to_proc
>> %w{ a b }.map(&:capitalize) => ["A", "B"]
:capitalize 这个符号会被解释成发送给数组里面每个项目的信息。相当于:
%w{ a b }.map {|str| str.capitalize}
也相当于:
%w{ a b }.map {|str| str.send(:capitalize)}
可以去掉括号:
%w{ a b }.map &:capitalize
Procs 作为闭包
在方法主体里用的本地变量,跟调用方法的时候使用的本地变量不一样。
def talk a = "hello" puts a end a = "goodbye" # 输出的是 hello talk # 输出的是 goodbye puts a
a 这个标识符被分配使用了两次,不过两次分配之间没什么联系。
在代码块里使用已经存在的变量:
>> m = 10 => 10 >> [1,2,3].each {|x| puts x * m} 10 20 30 => [1, 2, 3]
multiply_by
返回了一个 proc,调用 multiply_by
方法的时候,传递给这个方法的参数,会保留在 proc 里面:
def multiply_by(m) Proc.new {|x| puts x * m} end mult = multiply_by(10) # 输出 120 mult.call(12)
再做个实验,注意下面两个变量 a:
def call_some_proc(pr) a = "在方法作用域里的 'a'" puts a pr.call end a = "在 Proc 区块里使用的 'a'" pr = Proc.new { puts a } pr.call call_some_proc(pr)
输出的结果是:
在 Proc 区块里使用的 'a' 在方法作用域里的 'a' 在 Proc 区块里使用的 'a'
Proc 对象会带着它的上下文。在上面的例子里,a 就是这个上下文里的一个部分,这个 a 会一直在 Proc 里存在。像这样的一块代码,一直带着它创建时的上下文,就是一个闭包(closure)。调用闭包,就会打开闭包,它里面会包含你创建它的时候放进去的东西。闭包会保留程序运行的部分状态。
创建一个闭包,有点像是打包行李,不管你在哪里打开这个行李包,它里面都会包含你打包的时候放进去的东西。
看一下计数器的例子,每次调用 proc 的时候它的变量的值都会增加一:
def make_counter n = 0 return Proc.new { n += 1 } end c = make_counter puts c.call puts c.call d = make_counter puts d.call puts c.call
输出的是:
1 2 1 3
Proc 参数
一个 Proc,带个区块,区块里有个参数:
pr = Proc.new {|x| puts "调用时的参数:#{x}" } pr.call(100)
输出的是:
调用时的参数:100
Proc 的参数与方法处理参数的方式不一样。它不乎调用时使用的参数的数量是否正确。支持一个参数:
>> pr = Proc.new {|x| p x } => #<Proc:0x007faf7aa016e0@(irb):27>
调用时不使用参数:
>> pr.call nil
调用时使用了多个参数,只取第一个参数,扔掉剩下的:
>> pr.call(1,2,3) 1
用 lambda 与 ->
创建函数
lambda 方法返回一个 Proc 对象。 提供的代码块会成为函数的主体:
>> lam = lambda { puts "a lambda!" } => #<Proc:0x007faf7a8afd28@(irb):66 (lambda)> >> lam.call a lambda! => nil
lambda 口味的 proc 与一般的 proc 有三个不一样的地方。lambda 需要显式创建,隐式创建的不会是 lambda,比如像这样:
def m(&block)
lambda 对待 return 关键词与普通的 proc 也不一样。
def return_test l = lambda { return } l.call puts "still here!" p = Proc.new { return } p.call puts "you won't see this message!" end return_test
输出的是 “still here! ”,不会看到第二条信息。因为调用 Proc 对象会触发从 return_test
那里返回。不过调用 lambda 触发的是从 lambda 主体的返回(退出),方法的执行仍会继续。
lambda 口味的 proc ,调用的时候要使用正常数量的参数:
>> lam = lambda {|x| p x} => #<Proc:0x007faf7a998c58@(irb):105 (lambda)> >> lam.call(1) 1 => 1 >> lam.call ArgumentError: wrong number of arguments (given 0, expected 1) from (irb):105:in `block in irb_binding' from (irb):107 from /usr/local/bin/irb:11:in `<main>' >> lam.call(1,2,3) ArgumentError: wrong number of arguments (given 3, expected 1) from (irb):105:in `block in irb_binding' from (irb):108 from /usr/local/bin/irb:11:in `<main>'
->
>> lam = -> { puts "hi" } => #<Proc:0x007faf7a903400@(irb):109 (lambda)> >> lam.call hi => nil
参数放到括号里:
>> mult = ->(x,y) { x * y } => #<Proc:0x007faf7a980720@(irb):111 (lambda)> >> mult.call(3,4) => 12
作为对象的方法
方法不是对象,不过你可以对象化(objectify)它们,让它们成为对象。
捕获方法对象
使用 method 方法,把方法的名字作为它的参数。
class C def talk puts "获取方法对象,self 是 #{self}。" end end c = C.new meth = c.method(:talk)
meth 是方法对象,绑定了 c 对象的 talk 方法。调用它:
>> meth.call 获取方法对象,self 是 #<C:0x007faf7a950f48>。
重新绑定:
class D < C end d = D.new unbound = meth.unbind unbound.bind(d).call
结果是:
获取方法对象,self 是 #<D:0x007faf7a84c430>。
也可以这样重新绑定:
unbound = C.instance_method(:talk)Ruby