&: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