block, proc, lambda
&:to_i是一个block,block不能独立存在,同时你也没有办法直接存储或传递它,
必须把block挂在某个方法后面。
一、从最简单的看起
def f1
yield
end
def f2(&p)
p.call
end
def f3(p)
p.call
end
f1 { puts "f1" }
f2 { puts "f2" }
f3(proc{ puts "f3" })
f3(Proc.new{ puts "f3" })
f3(lambda{ puts "f3" })
f3(->{ puts "f3" })
由于 &p 并不作为方法的参数,所以f2
不能传递一个参数,f2需要的是一个block。
&p 相等于一种申明,当方法后面有block的时候,会把block捕捉进来。
f3 需要一个Proc的参数,所以就需要传递一个Proc进去。
二、block
block,ruby中的block是方法一个重要但非必要的组成部分,我们可以认为方法
的完整定义类似于
def f(零个或多个参数,&p)
...
end
注意&p不是参数,&p类似于一种声明,当方法后面有block时,会将block捕捉起来
存放在变量p中,如果方法后面没有block,那么&p什么也不干。
>> def f(&p)
>> end
=> :f
>> f(1)
ArgumentError: wrong number of arguments (1 for 0)
from (irb):72:in `f'
from (irb):74
from /Users/wanghao/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
>> f
=> nil
>> f(){ puts "f" }
=> nil
>> f()
=> nil
从以上代码的运行结果可以知道&p不是参数
>> def f(a)
>> puts a
>> end
=> :f
>> f(1) { puts 2 }
1
=> nil
所以任何方法都可以挂载一个block,如果你定义的方法想使用block做点事情,
那么你需要使用yield
关键字或&p
>> def f1
>> yield
>> end
=> :f1
>> def f2(&p)
>> p.call
>> end
=> :f2
>> f1
LocalJumpError: no block given (yield)
from (irb):88:in `f1'
from (irb):93
from /Users/wanghao/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
>> f2
NoMethodError: undefined method `call' for nil:NilClass
from (irb):91:in `f2'
from (irb):94
from /Users/wanghao/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
>>
为了保证不抛出异常,我们可以这么修改
def f1
yield if block_given?
end
def f2(&p)
p.call if block_given?
end
这样,f1和f2后面无论挂不挂block都不会抛异常了。
def f2(&p)
puts p.class
puts p.inspect
p.call
end
>> f2{->{ puts "123" }}
Proc
#<Proc:0x007fb57c0884d0@(irb):102>
=> #<Proc:0x007fb57c088390@(irb):102 (lambda)>
["1", "2", "3"].map(&:to_i),其效果和["1", "2", "3"].map {|i| i.to_i }一样, 但简洁了许多,并且更加拉风。
这里的魔法在于符号&会触发:to_i的to_proc方法, to_proc执行后会返回一个proc实例, 然后&会把这个proc实例转换成一个block,我们需要要明白map方法后挂的是一个block,而不是接收一个proc对象做为参数。&:to_i是一个block,block不能独立存在,同时你也没有办法直接存储或传递它,必须把block挂在某个方法后面。
:to_i是Symbol类的实例,Symbol中的to_proc方法的实现类似于
class Symbol
def to_proc
Proc.new { |obj| obj.send(self) }
end
end
给自己的类编写to_proc 方法,然后使用&耍下酷,比如
>> class AddBy
>> def initialize(num = 0)
>> @num = num
>> end
>> def to_proc
>> Proc.new { |obj| obj.send('+', @num) }
>> end
>> end
=> :to_proc
>> add_by_9 = AddBy.new(9)
=> #<AddBy:0x007fba3997c2c8 @num=9>
>> puts [1,2,3].map(&add_by_9)
10
11
12
=> nil
ruby中,block 存在形式
do |...|
...
end
有时候是这样
{|...| ...}
或者类似 &p, &:to_i, &add_by_9 之类,但是它无体,无体
的意思就是说block无法单独存在,必须挂在方法后面,并且你
没有办法直接把它存到变量中,也没有办法直接将它作为参数
传递给方法,所以当你想存储,传递block时,你可以使用proc
对象了。
p = Proc.new(&:to_i)
p = Proc.new {|obj| obj.to_i }
p = Proc.new do |obj|
obj.to_i
end
p = proc(&:to_i)
p = proc {|obj| obj.to_i}
p = proc do |obj|
obj.to_i
end
我们经常在该挂block的时候,却把proc对象当参数传给方法了,
或者不明白&p就是block可以直接交给方法使用
** &p是block, p是proc 不到万不得已的情况下不要显式地创建proc **
三、lambda
lambda是匿名方法,lambda和proc也是两种不同的东西,但是在
ruby中lambda只能依附proc而存在,这点和block不同,block并不
依赖proc
>> p = Proc.new {}
=> #<Proc:0x007fba3a8dd708@(irb):11>
>> puts p
#<Proc:0x007fba3a8dd708@(irb):11>
=> nil
>> l = lambda {}
=> #<Proc:0x007fba3a8cd498@(irb):13 (lambda)>
>> puts l
#<Proc:0x007fba3a8cd498@(irb):13 (lambda)>
从这里可以理解ruby的设计者们确实在有意的区分
lambda和proc,并不想把lambda和proc混在一起,如同ruby中
没有叫Block的类,除非你自己定义一个,ruby中也没有叫
Lambda的类,于是将lambda对象化的活儿就交给了Proc。
当你用lambda弄出一个匿名方法时,发现它是一个proc对象,并且
这个匿名方法能干的活,proc对象都能做,于是我们不淡定了。
Proc.new{} 这样可以
proc {} 这样也没有问题
lambda {} 这样做也不错
-> {} 这个还是能行
lambda 和 proc 之间的区别除了那个经常用做面试题的return之外
还有一个区别就是lambda 不能完美的转换为block。而proc可以完美
的转换为block,注意,我说的lambda指的是lambda方法或者->符号生成
的proc,当然和方法一样lambda是严格检查参数的,这个特点也和proc不一样。
def f0()
p = Proc.new { return 0}
p.call
1
end
def f1()
l = lambda { return 0}
l.call
1
end
f0 # 返回0
f1 # 返回1
如果你能够理解proc在行为上更像block,lambda其实就是方法只不过是匿名的,那么你对上面的结果不会感到惊讶。
如果把f0,f1做一些修改,就更容易理解上面的结果了。
def f0()
return 0
1
end
def f1()
def __f1
return 0
end
__f1
1
end
f0 # 返回0
f1 # 返回1
验证proc不需要参数校验,而lambda需要参数校验。
>> def f5()
>> yield 1,2
>> end
=> :f5
>> p1 = proc { |i,j| puts j }
=> #<Proc:0x007fba3a856370@(irb):42>
>> p2 = proc { |i| puts i}
=> #<Proc:0x007fba398f0c28@(irb):43>
>> l1 = lambda { |i,j| puts j }
=> #<Proc:0x007fba398e0580@(irb):44 (lambda)>
>> l2 = lambda { |i| puts i }
=> #<Proc:0x007fba3a845160@(irb):45 (lambda)>
>> f5(&p1)
2
=> nil
>> f5(&p2)
1
=> nil
>> f5(&l1)
2
=> nil
>> f5(&l2)
ArgumentError: wrong number of arguments (2 for 1)
from (irb):45:in `block in irb_binding'
from (irb):40:in `f5'
from (irb):49
from /Users/wanghao/.rvm/rubies/ruby-2.2.2/bin/irb:11:in `<main>'
>>
四. 总结
-
block和Proc都是代码块,Proc可以复用,block不可以复用
-
block和lambda不是类,Proc是类,所以block和lambda是小写
-
lambda 是匿名函数
-
lambda对参数个数验证,Proc不会验证
-
lambda会执行return,Proc遇到return会中断
-
lambda不会转换成block,Proc可以转换成block
方法定义中&p参数,相等于声明,如果后面包括block的话,就会将后面的block捕捉进来放在p中
任何方法都可以挂载block,如果想要block做一些事情,就需要在定义方法的时候,指定yield或&p
-
["1", "2", "3"].map(&:to_i) 相等于 ["1", "2", "3"].map{ |obj|
obj.to_i }
这里的魔法在于符号&会触发:to_i的to_proc方法,to_proc方法执行后
会返回一个proc实例,然后&会把这个proc转换成block。并且map后面跟
的也是一个block -
&p中,&p是block,而p是proc