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

標籤

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([123]) { |element| puts element }  
要用程式流程來示意的話,Ruby 會使用以下建立 Proc 物件: 
  1. p = Proc.new { |element| puts element }  
再呼叫方法: 
  1. foreach([123], &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) # [102030]  
例如 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 的結果。

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!