blocks, Procs, Methods, lambdas(也稱閉包)是Ruby中最強大的一部分,用過你就會知道,同時也是最容易迷惑的。
這多是由於Ruby處理閉包的方式有點怪。更甚的是,Ruby有4種處理閉包的方式, 第一次用,每種都不太順手。 數據庫
最多見、最簡單、最富爭議、最有Ruby風格的方式是blocks。寫法以下: 數組
array = [1, 2, 3, 4] array.collect! do |n| n ** 2 end puts array.inspect # => [1, 4, 9, 16]
do…end構成一個block。而後把這個block經過collect!傳給一個數組。就可使用block中的n來迭代數組中每一個元素。
collect!是Ruby庫裏的方法,下面咱們來寫一個本身的相似方法iterate! ruby
class Array def iterate! self.each_with_index do |n, i| self[i] = yield(n) end end end array = [1, 2, 3, 4] array.iterate! do |n| n ** 2 end puts array.inspect # => [1, 4, 9, 16]
首先,咱們打開Array,並添加進iterate!方法。方法名以!結尾表示危險方法,引發注意。如今咱們就可能像使用collect!同樣使用iterate! 閉包
與屬性不一樣,在方法中不須要指定block的名字,而是使用yield來調用。yield會執行block中的代碼。同時,注意咱們是怎麼把n(each_with_index當前處理的數字)傳給yield的。傳給yield的參數即對應了block中的參數(||中的部分)。如今n就能被block調用並在yield調用中返回n**2。
整個調用以下:
一、一個整數組成的數組調用iterate!
二、當yield被調用時,把n(第一次爲1,第二次爲2,…)傳給block
三、block對n進行n**2。由於是最後一行,自動做爲結果返回。
四、yield獲得block的結果,並把值重寫到array裏。
五、數據中每一個對象執行相同操做。 函數
以上僅僅是個開始,yield只是調用block的一種方式,還有一種叫Proc,看看。 spa
class Array def iterate!(&code) self.each_with_index do |n, i| self[i] = code.call(n) end end end array = [1, 2, 3, 4] array.iterate! do |n| n ** 2 end puts array.inspect # => [1, 4, 9, 16]
和上一段代碼只有兩個不一樣
一、爲iterate!傳遞一個參數&code,&表示這個參數是block。
二、在iterate!中沒有使用yield而是call。
結果相同,爲何還要這種不一樣的語法呢?讓咱們先來看一個到底什麼是blocks吧? 翻譯
def what_am_i(&block) block.class end puts what_am_i {} # => Proc
block居然是Proc!那Proc是什麼? code
blocks很簡單,但當咱們須要處理不少blocks,屢次使用一個blocks時,咱們不得不重複代碼。既然Ruby是徹底面向對象的,咱們就能把這些可複用的代碼保存成object。這段可複用的代碼就是Proc(procedure的簡稱)
block與Proc唯一的不一樣是:block是不能保存的Proc,一性的。 對象
class Array def iterate!(code) self.each_with_index do |n, i| self[i] = code.call(n) end end end array_1 = [1, 2, 3, 4] array_2 = [2, 3, 4, 5] square = Proc.new do |n| n ** 2 end array_1.iterate!(square) array_2.iterate!(square) puts array_1.inspect puts array_2.inspect # => [1, 4, 9, 16] # => [4, 9, 16, 25]
注意:並無在 iterate!的參數頭部添加&,由於Proc只是一個普通類,不須要特殊處理。 three
上面的方式也是大多數語言處理閉包的方式。
而block是Ruby特有的方式。
另外Ruby不僅使用blocks作閉包還有一個緣由。好比有時咱們須要傳遞多個閉包給一個方法,這時block立刻力不從心了。但咱們能夠用Proc:
def callbacks(procs) procs[:starting].call puts "Still going" procs[:finishing].call end callbacks(:starting => Proc.new { puts "Starting" }, :finishing => Proc.new { puts "Finishing" }) # => Starting # => Still going # => Finishing
因此,何時用blocks而不用Procs呢?我通常這樣判斷:
一、Block:方法把一個對象拆分紅不少片斷,而且你但願你的用戶能夠與這些片斷作一些交互。
二、Block:但願自動運行多個語句,如數據庫遷移(database migration)。
三、Proc:但願屢次複用一段代碼。
四、Proc:方法有一個或多個回調方法(callbacks)。
爲何block小寫,而Proc大寫
這只是我我的習慣。由於Proc是Ruby中的一個相似,而blocks並無本身的類(本質上只是Procs),只是一種語法規則。後面的lambda 小寫也是如此。
上面的Procs與blocks用法很像其它語言中的匿名函數(即lambdas)。Ruby也支持lambdas.
class Array def iterate!(code) self.each_with_index do |n, i| self[i] = code.call(n) end end end array = [1, 2, 3, 4] array.iterate!(lambda { |n| n ** 2 }) puts array.inspect # => [1, 4, 9, 16]
lambdas看起來很像Procs,但它們有2個細微的區別。
一、lambdas檢查參數的個數,Procs不會。
def args(code) one, two = 1, 2 code.call(one, two) end args(Proc.new{|a, b, c| puts "Give me a #{a} and a #{b} and a #{c.class}"}) args(lambda{|a, b, c| puts "Give me a #{a} and a #{b} and a #{c.class}"}) # => Give me a 1 and a 2 and a NilClass # *.rb:8: ArgumentError: wrong number of arguments (2 for 3) (ArgumentError)
能夠看到,在Proc中,多餘的參數被設爲nil。但lambdas中,Ruby拋出了一個錯誤。
二、return不一樣。lambdas的return是返回值給方法,方法會繼續執行。Proc的return會終止方法並返回獲得的值。有點拗口,下面看例子。
def proc_return Proc.new { return "Proc.new"}.call return "proc_return method finished" end def lambda_return lambda { return "lambda" }.call return "lambda_return method finished" end puts proc_return puts lambda_return
proc_return中,執行到Proc.new中的return時,直接返回」Proc.new」,不繼續執行。
lambda_return中,執行到lambda中的return時,返回」lambda」,方法繼續執行。
爲何會有這樣的不一樣?
答案在於procedures和methods概念上的不一樣。
Ruby中的Procs是代碼片斷(code snippets),不是方法。所以,Proc的return就是整個方法的return。
但lambdas就像是單獨的methods(只不過是匿名的),因此它要檢查參數個數,且不會覆蓋整個方法的返回。
所以,最好把lambdas看成另外一種methods的寫法,一種匿名的方式。
因此,何時用lambda而不是Proc呢?能夠參考下面代碼:
def generic_return(code) code.call return "generic_return method finished" end puts generic_return(Proc.new { return "Proc.new" }) puts generic_return(lambda { return "lambda" }) # => *.rb:6: unexpected return (LocalJumpError) # => generic_return method finished
Ruby語法中通常參數(例子中爲Proc)不能含有return。但使用了lambda後能夠用return。
還能夠參考:
def generic_return(code) one, two = 1, 2 three, four = code.call(one, two) return "Give me a #{three} and a #{four}" end puts 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:9: unexpected return (LocalJumpError) # => Give me a 4 and a # => Give me a 3 and a 4
使用lambda,代碼很天然。但若是用Proc,咱們須要對Arrays進行賦值。
當你想把一個方法以閉包的形式傳遞給另外一個方法,而且保持代碼DRY。你可使用Ruby的method方法。
class Array def iterate!(code) self.each_with_index do |n, i| self[i] = code.call(n) end end end def square(n) n ** 2 end array = [1, 2, 3, 4] array.iterate!(method(:square)) puts array.inspect # => [1, 4, 9, 16]
例子中,咱們先有了一個square方法。咱們能夠把它轉換成一個Method對象並以參數形式傳遞給iterate!方法。但,這個新對象屬於哪一個類呢?
def square(n) n ** 2 end puts method(:square).class # => Method
如你所料,square不是Proc,而是Method。Method與lambda用法相同,由於它們的概念是同樣的。不一樣的是Method是有名字的method,而lambda是匿名method.
到此爲止,咱們已經瞭解了Ruby的4種閉包類型:blocks, Procs, lambdas 和 Methods。
blocks和Procs看起來像在代碼中插入代碼片斷。而lambdas和Methods看起來像方法。
經過幾個例子和比較,但願你能瞭解如何靈活運用閉包,遊刃有餘!
譯至:http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/ 翻譯很辛苦,轉載請附連接:)