>> 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 `
->
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 }
=> #
调用时不使用参数:
>> pr.call
nil
调用时使用了多个参数,只取第一个参数,扔掉剩下的:
>> pr.call(1,2,3)
1
用 lambda 与 -> 创建函数
lambda 方法返回一个 Proc 对象。 提供的代码块会成为函数的主体:
>> lam = lambda { puts "a 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}
=> #
>> 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 `
>> 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 `
->
>> lam = -> { puts "hi" }
=> #
>> lam.call
hi
=> nil
参数放到括号里:
>> mult = ->(x,y) { x * y }
=> #
>> 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 是 #
重新绑定:
class D < C
end
d = D.new
unbound = meth.unbind
unbound.bind(d).call
结果是:
获取方法对象,self 是 #
也可以这样重新绑定:
unbound = C.instance_method(:talk)