深入理解Blocks,Procs和lambdas
生活随笔
收集整理的這篇文章主要介紹了
深入理解Blocks,Procs和lambdas
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
首發在:[url]http://blog.enjoyrails.com/?p=125[/url]原文:[url]http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/[/url]
2010.7.17 :在google上搜索一些東西的時候,發現有個網易博客的杭州垃圾轉載了這篇文章,說他是垃圾,第一沒有聯系我,第二沒有添加我文章的鏈接。我又把這篇文章升級到了Ruby1.9版本的,你繼續轉載啊,垃圾! 再次敬告:轉載請注明原文鏈接!
Blocks, Procs和lambdas是Ruby里最重要的方面,同時也是難點之一。這是因為Ruby處理閉包(closures)的方式比較特別。更復雜的是,Ruby有4種使用閉包的方式,它們之間還稍有不同。
First Up, Blocks
大多數情況下,使用閉包最Ruby的方式就是用Block了。
array = [1,2,3,4]#[1, 2, 3, 4]array.collect! do |n|n ** 2 (求平方)end#[1, 4, 9, 16]puts array.inspect# [1, 4, 9, 16]
so ,what’s going on here?
我們發送了‘collect!’方法給一個block代碼的數組
在collect!方法內部給每個變量作平方(此處為n)
我們現在來實現自己的collect!方法,我們先建立一個iterate!方法:
class Arraydef iterate!self.each_with_index do |n,i|self[i] = yield(n)endendendarray = [1,2,3,4]#[1, 2, 3, 4]array.iterate! do |n|n ** 2end#[1, 4, 9, 16]
和屬性不一樣,你并不需要在你的方法內部指定block名字,你只需要使用yield關鍵字就ok了。請注意上例是如何給yield傳參數n的。整個過程可以解釋為:
給Array對象發送iterate!方法
當yield被調用的時候,把n(第一次1,第二次4,以此類推)傳給被給的block里
block里有了可用的值,也就是n,那么就平方它。
yield輸出block里返回的值,并且重寫了array里面的值。
使用yield只是使用block代碼的一個方法而已,這里還有另外一個,那就是作為一個Proc來調用它。看看:
class Arraydef iterate!(&code)self.each_with_index do |n,i|self[i] = code.call(n)endendendarr = [1,2,3,4]arr.iterate! do |n|n ** 2end#[1, 4, 9, 16]
和上一個例子非常相似。然而有兩點不同。第一個就是iterate!的參數形式。第二點就是call方法。 輸出結果是相似的,但是為什么語法不同呢?
def what_am_i(&block)block.classendputs what_am_i{}#Proc這是一個Proc對象。block是一個Proc。<pre lang="RUBY">def what_am_iyield.classendputs what_am_i{ puts "x"}=> xNilClassputs what_am_i{ "x"}# String
這說明,使用yield直接返回的是block表達式里的值。
那么什么是Proc了?
Procedures, AKA, Procs
我們經常需要使用一個塊在多個地方,這樣我們就需要不斷的重復我們的block,那么為了解決這個問題,做到代碼復用,我們就可以使用Proc, 其實block和Proc的唯一區別就是, block是個一次性的Proc。
class Arraydef iterate!(code)self.each_with_index do |n, i|self[i] = code.call(n)endendendarr1 = [1,2,3,4]arr2 = [2,3,4,5]square = Proc.new do |n|n ** 2endarr1.iterate!(square)#[1, 4, 9, 16]arr2.iterate!(square)#[4, 9, 16, 25]
你可能會說,用block不就完了嗎?整個Proc不是多此一舉。 那么請問,如果我們想給一個方法傳多個closures怎么辦?使用block會很不靈活。 但是用Proc就好多了:
def callbacks(procs)procs[:starting].callputs "Still going"procs[:finishing].callendcallbacks(:starting #Proc.new{puts "Starting"},:finishing #Proc.new{puts "Finishing"})#StartingStill goingFinishing
那么什么時候用block,什么時候用Proc呢?Block: 當你的方法是被分成多個小片段,而你想讓你的用戶與這些片段做一些交互。Block: 當你需要運行多個原子性表達式的時候,比如數據庫遷移(migration)Proc : 當你需要重用一個block的時候。Proc : 當你的方法有一個或多個callbacks的時候。Lambdas匿名函數,在其他語言里也有。Ruby里也可用:
class Arraydef iterate(code)self.each_with_index do |n,i|self[i] = code.call(n)endendendarr = [1,2,3,4]arr.iterate(lambda{|n| n**2 })#[1, 4, 9, 16]
咋看上去,lambda和Proc執行的好像是一樣。但是這里有兩個細微的不同點。第一個不同就是lambda檢查參數的個數。
def arguments(code)one, two = 1, 2code.call(one,two)endarguments(Proc.new { |a, b, c| puts "Give me a #{a} and a #{b} and a #{c.class}" })#Give me a 1 and a 2 and a NilClassarguments(lambda { |a, b, c| puts "Give me a #{a} and a #{b} and a #{c.class}" })#ArgumentError: wrong number of arguments (2 for 3)
第二個不同就是,lambda有一個‘微小’的返回,什么意思呢?Proc返回會阻止一個方法的執行,并返回這個值。lambda返回它們的值,但是方法還會繼續執行。看看例子:
def proc_returnProc.new { return "Proc.new"}.callreturn "proc_return method finished"enddef lambda_returnlambda{ return "lambda"}.callreturn "lambda_return method finished"endputs proc_return ? #Proc.newputs lambda_return ?#lambda_return method finished
為什么會不同?答案就是procedures(過程)和methods(方法)的概念不同。Procs在Ruby里是代碼片段,不是方法。因為這個,Proc return的就是proc_return這個方法的return,因為Proc那段就是那個方法里的代碼片段。而lambdas的行為像一個方法,它檢查參數的個數,而且不會覆蓋調用方法的return。由于這個原因,你最好把lambdas理解為寫一個方法的另類方式,只不過是匿名的而已。那么什么時候該用lamda代替Proc呢?看例子:
def generic_return(code)code.callreturn "generic_return method finished"endputs generic_return(Proc.new{return "Proc.new"})#LocalJumpError: unexpected returnputs generic_return(lambda{return "lambda"})# => generic_return method finished
Ruby語法里,參數部分不能帶return,然而lambda行為像方法,所以它可以有個內部return。
def generic_return(code)one, two ? ?= 1, 2three, four = code.call(one, two)endputs generic_return(lambda { |x, y| return x + 2, y + 2 })puts generic_return(Proc.new { |x, y| return x + 2, y + 2 })puts generic_return(Proc.new { |x, y| x + 2; y + 2 })puts generic_return(Proc.new { |x, y| [x + 2, y + 2] })# Give me a 3 and a 4#*.rb:11: unexpected return (LocalJumpError)#Give me a 4 and a# ?Give me a 3 and a 4
這個generic_return方法返回的是兩個值,使用lambda,一切都很easy,而使用Proc,我們還不得不用數組指定x,y。
a,b = ?generic_return(lambda { |x, y| return x + 2, y + 2 })#[3, 4]a#3b#4c,d = generic_return(Proc.new { |x, y| x + 2; y + 2 })#[4]c#4d#nile,f = generic_return(Proc.new { |x, y| [x + 2, y + 2] })#[3, 4]e#3f#4
Method Objects如果你有一個工作完好的方法,你想把它傳到另一個方法里,作為閉包,也為了使你的代碼DRY,那么你可以使用這個method方法:
class Arraydef iterate!(code)self.each_with_index do |n,i|self[i] = code.call(n)endendenddef square(n)n ** 2endarr = [1,2,3,4]arr.iterate!(method(:square))#[1, 4, 9, 16]puts method(:square).class# ?Method
這個方法對象的行為像是lambda,那么到底。。。:
puts lambda {}.class# ?Proc
呵呵,它是個Proc。現在明白Ruby 的這四種閉包方式了吧。block,Proc的行為更像一個代碼片段,lambdas和method方法的行為更像方法。block,Proc, lambdas都是Proc對象,而method,是Method對象。?
2010.7.17 :在google上搜索一些東西的時候,發現有個網易博客的杭州垃圾轉載了這篇文章,說他是垃圾,第一沒有聯系我,第二沒有添加我文章的鏈接。我又把這篇文章升級到了Ruby1.9版本的,你繼續轉載啊,垃圾! 再次敬告:轉載請注明原文鏈接!
Blocks, Procs和lambdas是Ruby里最重要的方面,同時也是難點之一。這是因為Ruby處理閉包(closures)的方式比較特別。更復雜的是,Ruby有4種使用閉包的方式,它們之間還稍有不同。
First Up, Blocks
大多數情況下,使用閉包最Ruby的方式就是用Block了。
array = [1,2,3,4]#[1, 2, 3, 4]array.collect! do |n|n ** 2 (求平方)end#[1, 4, 9, 16]puts array.inspect# [1, 4, 9, 16]
so ,what’s going on here?
我們發送了‘collect!’方法給一個block代碼的數組
在collect!方法內部給每個變量作平方(此處為n)
我們現在來實現自己的collect!方法,我們先建立一個iterate!方法:
class Arraydef iterate!self.each_with_index do |n,i|self[i] = yield(n)endendendarray = [1,2,3,4]#[1, 2, 3, 4]array.iterate! do |n|n ** 2end#[1, 4, 9, 16]
和屬性不一樣,你并不需要在你的方法內部指定block名字,你只需要使用yield關鍵字就ok了。請注意上例是如何給yield傳參數n的。整個過程可以解釋為:
給Array對象發送iterate!方法
當yield被調用的時候,把n(第一次1,第二次4,以此類推)傳給被給的block里
block里有了可用的值,也就是n,那么就平方它。
yield輸出block里返回的值,并且重寫了array里面的值。
使用yield只是使用block代碼的一個方法而已,這里還有另外一個,那就是作為一個Proc來調用它。看看:
class Arraydef iterate!(&code)self.each_with_index do |n,i|self[i] = code.call(n)endendendarr = [1,2,3,4]arr.iterate! do |n|n ** 2end#[1, 4, 9, 16]
和上一個例子非常相似。然而有兩點不同。第一個就是iterate!的參數形式。第二點就是call方法。 輸出結果是相似的,但是為什么語法不同呢?
def what_am_i(&block)block.classendputs what_am_i{}#Proc這是一個Proc對象。block是一個Proc。<pre lang="RUBY">def what_am_iyield.classendputs what_am_i{ puts "x"}=> xNilClassputs what_am_i{ "x"}# String
這說明,使用yield直接返回的是block表達式里的值。
那么什么是Proc了?
Procedures, AKA, Procs
我們經常需要使用一個塊在多個地方,這樣我們就需要不斷的重復我們的block,那么為了解決這個問題,做到代碼復用,我們就可以使用Proc, 其實block和Proc的唯一區別就是, block是個一次性的Proc。
class Arraydef iterate!(code)self.each_with_index do |n, i|self[i] = code.call(n)endendendarr1 = [1,2,3,4]arr2 = [2,3,4,5]square = Proc.new do |n|n ** 2endarr1.iterate!(square)#[1, 4, 9, 16]arr2.iterate!(square)#[4, 9, 16, 25]
你可能會說,用block不就完了嗎?整個Proc不是多此一舉。 那么請問,如果我們想給一個方法傳多個closures怎么辦?使用block會很不靈活。 但是用Proc就好多了:
def callbacks(procs)procs[:starting].callputs "Still going"procs[:finishing].callendcallbacks(:starting #Proc.new{puts "Starting"},:finishing #Proc.new{puts "Finishing"})#StartingStill goingFinishing
那么什么時候用block,什么時候用Proc呢?Block: 當你的方法是被分成多個小片段,而你想讓你的用戶與這些片段做一些交互。Block: 當你需要運行多個原子性表達式的時候,比如數據庫遷移(migration)Proc : 當你需要重用一個block的時候。Proc : 當你的方法有一個或多個callbacks的時候。Lambdas匿名函數,在其他語言里也有。Ruby里也可用:
class Arraydef iterate(code)self.each_with_index do |n,i|self[i] = code.call(n)endendendarr = [1,2,3,4]arr.iterate(lambda{|n| n**2 })#[1, 4, 9, 16]
咋看上去,lambda和Proc執行的好像是一樣。但是這里有兩個細微的不同點。第一個不同就是lambda檢查參數的個數。
def arguments(code)one, two = 1, 2code.call(one,two)endarguments(Proc.new { |a, b, c| puts "Give me a #{a} and a #{b} and a #{c.class}" })#Give me a 1 and a 2 and a NilClassarguments(lambda { |a, b, c| puts "Give me a #{a} and a #{b} and a #{c.class}" })#ArgumentError: wrong number of arguments (2 for 3)
第二個不同就是,lambda有一個‘微小’的返回,什么意思呢?Proc返回會阻止一個方法的執行,并返回這個值。lambda返回它們的值,但是方法還會繼續執行。看看例子:
def proc_returnProc.new { return "Proc.new"}.callreturn "proc_return method finished"enddef lambda_returnlambda{ return "lambda"}.callreturn "lambda_return method finished"endputs proc_return ? #Proc.newputs lambda_return ?#lambda_return method finished
為什么會不同?答案就是procedures(過程)和methods(方法)的概念不同。Procs在Ruby里是代碼片段,不是方法。因為這個,Proc return的就是proc_return這個方法的return,因為Proc那段就是那個方法里的代碼片段。而lambdas的行為像一個方法,它檢查參數的個數,而且不會覆蓋調用方法的return。由于這個原因,你最好把lambdas理解為寫一個方法的另類方式,只不過是匿名的而已。那么什么時候該用lamda代替Proc呢?看例子:
def generic_return(code)code.callreturn "generic_return method finished"endputs generic_return(Proc.new{return "Proc.new"})#LocalJumpError: unexpected returnputs generic_return(lambda{return "lambda"})# => generic_return method finished
Ruby語法里,參數部分不能帶return,然而lambda行為像方法,所以它可以有個內部return。
def generic_return(code)one, two ? ?= 1, 2three, four = code.call(one, two)endputs generic_return(lambda { |x, y| return x + 2, y + 2 })puts generic_return(Proc.new { |x, y| return x + 2, y + 2 })puts generic_return(Proc.new { |x, y| x + 2; y + 2 })puts generic_return(Proc.new { |x, y| [x + 2, y + 2] })# Give me a 3 and a 4#*.rb:11: unexpected return (LocalJumpError)#Give me a 4 and a# ?Give me a 3 and a 4
這個generic_return方法返回的是兩個值,使用lambda,一切都很easy,而使用Proc,我們還不得不用數組指定x,y。
a,b = ?generic_return(lambda { |x, y| return x + 2, y + 2 })#[3, 4]a#3b#4c,d = generic_return(Proc.new { |x, y| x + 2; y + 2 })#[4]c#4d#nile,f = generic_return(Proc.new { |x, y| [x + 2, y + 2] })#[3, 4]e#3f#4
Method Objects如果你有一個工作完好的方法,你想把它傳到另一個方法里,作為閉包,也為了使你的代碼DRY,那么你可以使用這個method方法:
class Arraydef iterate!(code)self.each_with_index do |n,i|self[i] = code.call(n)endendenddef square(n)n ** 2endarr = [1,2,3,4]arr.iterate!(method(:square))#[1, 4, 9, 16]puts method(:square).class# ?Method
這個方法對象的行為像是lambda,那么到底。。。:
puts lambda {}.class# ?Proc
呵呵,它是個Proc。現在明白Ruby 的這四種閉包方式了吧。block,Proc的行為更像一個代碼片段,lambdas和method方法的行為更像方法。block,Proc, lambdas都是Proc對象,而method,是Method對象。?
總結
以上是生活随笔為你收集整理的深入理解Blocks,Procs和lambdas的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么用lazy启动eclipse的时候
- 下一篇: 12月第四周安全回顾:双节期间微软忙补新