淺談Ruby中的block, proc, lambda, method object的區別

jopen 11年前發布 | 22K 次閱讀 Ruby Ruby開發

本文主要介紹了ruby開發中的比較容易混淆的幾個概念,并以簡單示例的形式展現他們之間的區別,明白了這些區別之后,在今后的開發中可以準確并優雅的使用它們。

當大家在百度中搜索“block proc lambda”的時候,會出來很多關于這幾個概念之間區別的介紹,既然搜索結果中已經有了這些介紹,那為什么還要寫這篇文章?

相信看過百度搜索結果中排名靠前的幾篇文章的同學,都會發現其實這些文章并沒有很好的說明他們之間區別是什么,大多只是介紹各自的用法,加上些許的區別,即使個別介紹了一些區別,也不夠系統,不夠深入。

正是基于上述原因,才醞釀了本文。本文以簡單示例的方式,詳細的介紹了它們之間的區別。相信您閱讀完本文,一定會豁然開朗,并在今后的開發中準確并優雅的使用它們。

由于時間,個人能力水平等有限,本文難免有錯誤或缺失之處,歡迎不吝指出。

block & proc

在介紹它們的區別之前,我們先來看一段有關block的簡單使用方法:

   def use_yield                                                                                         
       yield
   end
   
   use_yield do
       puts 'use yield'
   end
   
   def use_block_call(&block)
       block.call
  end
 
  use_block do
      puts 'use block call'
  end

以上介紹了兩種在函數中使用block的方式,第一種是通過yield來使用block,另外一種則是通過block.call來使用block。

proc全稱為procedure,中文翻譯為過程,步驟。關于block和proc的區別,我們先來看一個簡單的示例。

   def what_am_i(&block)                                                                                 
       block.class
   end
   puts what_am_i {}  # =< Proc

定義一個函數what_am_i并接受一個block,執行體中打印了block的類型,從執行的結果我們看到block的類型為proc,即說明block為proc的一種。

block和proc之間的區別主要有兩個:一是proc可以重復使用,如果某個block將在多個地方被用到,則我們可以將其定義為proc。另外一個區別就是當某個函數需要執行多個閉包的時候,此時我們就無法使用block而只有使用proc或其他的閉包。

示例如下,在執行某個具體的操作的前后,調用了我們自己的proc。

   def action(code)                                                                                      
       code[:before].call
       puts 'processing...'
       code[:after].call
   end
  
   action :before => Proc.new {puts 'before action'},
       :after => Proc.new {puts 'after action'}

proc & lambda

關于proc和lambda的區別,我們先來看一個簡單的例子。

   def args(code)                                                                                        
       code.call 'one','two'
   end
  
   args Proc.new { |a,b| puts a,b}
   args lambda {|a,b| puts a,b}

上述示例,第一眼看去發覺lambda和proc之間沒什么區別或者很難發現它們的區別是什么。

接下來,我們對上述示例做一下簡單的修改,我們將之前的接受兩個參數修改為三個,看看會發生什么情況。

   def args(code)
       code.call 'one','two'
   end
   
   args Proc.new { |a,b,c| puts a,b,c}
   args lambda {|a,b,c| puts a,b,c} # =< wrong number of arguments (2 for 3) (ArgumentError)

運行修改后的示例,發現lambda閉包出錯了,出錯的信息為,錯誤的參數個數。

至此,我們很快就發現了它們之間的一個區別是,lambda會檢查參數的個數,而proc則不會,proc默認將缺失的參數視為nil,并繼續執行代碼,這是為什么呢?在本節的最后,我們會道出這其中的緣由。

在了解到它們的第一個區別之后,接下來我們再來看一個示例。

   def proc_return?                                                                                      
       Proc.new {return 'Proc.new'}.call
       puts 'proc_return method finished'
   end
   
   def lambda_return
       lambda { return 'lambda'}.call
       puts 'lambda_return method finished'
   end
   
  puts proc_return # =< Proc.new
  puts lambda_return # =< lambda_return method finished

這個示例非常的簡單,我們定義了兩個函數proc_return以及lambda_return。這兩個函數一個用proc實現,另外一個用lambda實現,執行體都只有一行代碼,就是返回一段字符串。

執行的結果出乎我們的意料,proc并未執行return之后的代碼,而lambda執行了return之后的代碼。

綜上所述,我們得出了proc和lambda的兩個重要區別,一是lambda會進行參數個數的檢查而proc則不會,另外lambda會執行return之后的代碼而proc則不會。

為什么會出現上述情況,本質的原因在于,proc只是一段嵌入的代碼片段而lambda則是匿名函數,正因為是匿名函數,所以會檢查函數調用參數,并在函數調用結束之后,繼續執行后面的代碼,而proc由于是嵌入的一段代碼片段,在執行完return語句后,就已經返回,所以不再執行之后的代碼。

lambda & method object

   def greeting                                                                                          
       puts 'hello, method object'
   end
   
   method(:greeting).call
   
   lambda{puts 'hello, lambda'}.call

lambda和method object的用法基本一致,其唯一的區別在于lambda為匿名函數,而method object為命名函數。

總結

關于block,proc,lambda, method object這四者之間的區別可總結為以下:

block和proc本質上是一段嵌入的代碼塊,并非函數。而lambda和method object都是函數,只不過lambda是匿名函數,而method object為命名函數。

從本質上理解了它們的區別,我們在今后的開發中就會正確且優雅的運用它們。

引用

[1] http://www.reactive.io/tips/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/

相關代碼

來自:http://my.oschina.net/gschen/blog/325546

 

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!