2014年11月29日 星期六

[ Ruby Gossip ] Advance : 可執行物件 - 程式區塊與 Proc

Source From Here 
Preface 
在 迭代器與程式區塊 中談過,每次呼叫方法時,其實會涉及四個部份: 
* 訊息接收者
* . 運算
* 訊息名稱
* 程式區塊

程式區塊與 Proc 
如果你在方法最後一個參數設定 & 開頭的參數,則會使用區塊建立 Proc 物件傳入,Proc 有個 call 方法,用以執行 Proc 物件內含的程序。例如: 
 

你可以使用 Proc.new 建立程序物件。例如: 
>> p = Proc.new { |param| puts param }
=> #
>> p.call(123)
123
=> nil

Proc.new 會使用指定的程式區塊建立物件,呼叫 call 時,就是執行程式區塊指定的程式流程。要注意的是,Proc 物件是 Proc 物件,程式區塊是程式區塊,兩者根本上不同,程式區塊是定義方法時語法的一部份,呼叫方法時指定程式區塊,如果方法最後有個 & 參數,Ruby直譯器會使用方法上指定的程式區塊來建立 Proc 物件。例如: 
  1. foreach([1, 2, 3]) { |element| puts element }  
要用程式流程來示意的話,Ruby 會使用以下建立 Proc 物件: 
  1. p = Proc.new { |element| puts element }  
再呼叫方法: 
  1. foreach([1, 2, 3], &p)  
注意最後那個 &,這表示將 p 傳給方法最後一個 & 參數,少了那個 &,那麼 p 就只會是一般的方法呼叫引數: 
 

所以任何可以接受程式區塊的方法,如果想要自行建立 Proc 物件傳入,都要加上個 &。例如你有個想重用的程序,則可以使用 Proc 而不是程式區塊: 
>> puts_proc = Proc.new { |element| puts element }
=> #
>> [1, 2, 3].each(&puts_proc)
1
2
3
=> [1, 2, 3]

>> "abc".each_char(&puts_proc)
a
b
c
=> "abc"

不過如下指定就錯了,因為方法最後一個參數不知道該使用傳入的 Proc,還是捕捉程式區塊而建立的 Proc: 
 

實際上,& 會觸發物件的 to_proc 方法,並嘗試指定給 & 變數,你可以在任何物件上定義 to_proc 方法,然後使用 & 來觸發 to_proc 方法。例如: 
  1. class Ball  
  2.     attr_reader :radius  
  3.   
  4.     def initialize(radius)  
  5.         @radius = radius  
  6.     end  
  7.   
  8.     def self.to_proc  
  9.         Proc.new { |ball| ball.radius }  
  10.     end  
  11. end  
  12.   
  13. # 收集球的半徑  
  14. print [Ball.new(10), Ball.new(20), Ball.new(30)].collect(&Ball) # [10, 20, 30]  
例如 Symbol 上就定義有 to_proc 方法,若有個程式是如下: 
>> ["justin", "monica"].each { |name| name.capitalize! }
=> ["Justin", "Monica"]

則可以改用以下: 
>> :capitalize.to_proc.call("orz")
=> "Orz"
>> ["justin", "monica"].each(&:capitalize!)
=> ["Justin", "Monica"]
>> :not_exist.to_proc.call("test")
NoMethodError: undefined method `not_exist' for "test":String # String 物件上沒有方法 "not_exist"

有些方法可以直接傳入 Symbol 的,也是類似的道理。例如陣列的 reduce 方法,為了方便,甚至設計為可省略 &: 
>> [1, 2, 3].reduce { |sum, element| sum += element }
=> 6
>> [1, 2, 3].reduce(&:+)
=> 6
>> [1, 2, 3].reduce(:+)
=> 6
實際上,Symbol 的設計大致是: 
  1. class Symbol  
  2.     def to_proc  
  3.         Proc.new { |o| o.send(self) }  
  4.     end  
  5. end  
因此總能找出正確的回應方法來執行。 

Proc 的 call 方法可以接受任意引數,不過實際上你可以取得幾個引數,在於你定義了幾個區塊參數。例如: 
 

Proc 正如其名,是一小段程序,一小段流程,要注意若在建立 Proc 時的程式區塊 return 時的狀況。例如: 
 

注意到並沒有顯示 "some 2",因為上例相當於: 
  1. def some  
  2.     puts "some 1"  
  3.     puts "執行 Proc"  
  4.     return  
  5.     puts "some 2"  
  6. end  
上例中,Proc 是在 some 的作用範圍中建立,如果 Proc 沒有在作用範圍中建立,建立 Proc 時的程式區塊中若有 return,則會引發 LocalJumpError: 
 

因為設計 API 時,並不希望有 return 中斷了原本 API 的執行流程,因此 Ruby 執行時如果看到 return,就會視為錯誤,即使 return 的目的是正常結束並傳回值,如果你確實是想傳回值,可以不撰寫 return,因為 Ruby 執行流程中最後一個物件就會被當作傳回值。例如: 
 

因為 Proc 像是個執行流程而不是方法,除了要注意 return 之外,迭代器與程式區塊 中也提到,要注意程式區塊中撰寫了 break、next 或 redo 的結果。

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...