程式扎記: [ Ruby Gossip ] Basic : 方法 - 迭代器與程式區塊

標籤

2014年10月23日 星期四

[ Ruby Gossip ] Basic : 方法 - 迭代器與程式區塊

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

迭代器與程式區塊 
至今看過幾個具有程式區塊的例子。例如 loop 方法: 
  1. loop do  
  2.     puts "Orz"  
  3. end  
如果你要自行定義相同功能的方法,可以如下: 
  1. def forever  
  2.     while true  
  3.         yield  
  4.     end  
  5. end  
  6.   
  7. forever do  
  8.     puts "Orz"  
  9. end  
以上定義了 forever 迭代器,如果呼叫 forever 時緊接著包括程式區塊,forever 方法中執行yield一次,就會執行指定的程式區塊一次,do..end 也可以使用 {} 取代。例如: 
  1. forever { puts "Orz" }  
{} 的區塊寫法與 do..end 是幾乎相同的意義,慣例中,在一行中可寫完的區塊可使用 {},有傳回值的時候也可使用 {},do..end 則通常用於具有邊際效應的程式區塊。 

如果區塊中需要提供額外資訊,則可以定義 區塊參數Block parameter)。例如: 
  1. def for_with_index(from, to, step)  
  2.     i=from  
  3.     for i in (from..to).step(step)  
  4.         yield(i)  
  5.     end  
  6. end  
  7.   
  8. for_with_index(0102do |index|  
  9.     puts index  
  10. end  
區塊參數以 || 包括,在 yield 時若傳入引數,執行區塊時就會設定給區塊參數,若有多個區塊參數,則以逗號區隔,yield 指定引數時也是以逗號區隔。區塊參數也可以設定預設引數或者是接受不定長度引數的區塊參數,規則與定義方法參數相同,可參考 def 定義方法 中的說明,只不過無論 yield 時有無指定引數,區塊參數都是非必要的。例如: 
  1. class Integer  
  2.     def my_times  
  3.         for i in 0...self  
  4.             yield(i)  
  5.         end  
  6.     end  
  7. end  
  8.   
  9. 5.my_times do  
  10.     puts "XD"  
  11. end  
  12.   
  13. 5.my_times do |i|  
  14.     puts "#{i} - XD"  
  15. end  
程式區塊若有傳回值,則會成為 yield 的傳回值。例如,想實作陣列的 reduce 方法,可以如下: 
  1. class Array  
  2.     def my_reduce(value = 0)  
  3.         for i in 0...self.length  
  4.             # 不同環境中 self 代表不同對象,在這邊 self 代表陣列實例。  
  5.             value = yield(value, self[i])  
  6.         end  
  7.         value  
  8.     end  
  9. end  
  10.   
  11. puts [12345].my_reduce { |sum, value|  
  12.     sum + value  
  13. }  
如果程式區塊中撰寫了 break、next 或 redo,相當於在 yield 處直接 break、next 或 redo,例如: 
  1. class Integer  
  2.     def my_times  
  3.         # 不同環境中 self 代表不同對象,在這邊 self 代表 Integer 實例。  
  4.         for i in 0...self  
  5.             yield(i)  
  6.         end  
  7.     end  
  8. end  
  9.   
  10. 5.my_times do |i|  
  11.     puts "#{i} - XD"  
  12.     if i == 3  
  13.         break  
  14.     end  
就某種程度來說,這相當於這麼寫: 
  1. class Integer  
  2.     def my_times  
  3.         for i in (0..self - 1)  
  4.             puts "#{i} - XD"  
  5.             if i == 3  
  6.                 break  
  7.             end  
  8.         end  
  9.     end  
  10. end  
就底下這個例子而言,使用 {} 與 do..end 看不出什麼差別: 
 

但 {} 與 do..end 的寫法,在結合其它方法時,要注意執行順序。例如: 
 

each 執行完會傳回原陣列,因此第一個例子在顯示完 123 後,傳回的陣列被 puts 再分別換行顯示出來。第二個例子則是被照以下順序解釋: 
 

[1, 2, 3].each 會先被執行,然後進行 puts,其實你的區塊是傳給了puts,而不是傳給 each,當你傳遞區塊給一個方法時,而該方法沒有 yield,則傳遞的區塊只是被忽略。 

如果你在方法最後一個參數設定 & 開頭的參數,則會使用區塊建立 Proc 物件傳入,你可以用以判斷是否有傳入區塊,或者將代表區塊的物件,再傳給另一方法。例如: 
  1. # encoding: utf-8  
  2. def for_with_index(from, to, step, &block)  
  3.     if !block  
  4.         return  
  5.     end  
  6.     for i in (from..to).step(step)  
  7.         yield(i)  
  8.     end  
  9. end  
  10.   
  11. for_with_index(0102do |index|  
  12.     puts index  
  13. end  
  14.   
  15. puts "沒有區塊的for_with_index 開始"  
  16. for_with_index(0102)  
  17. puts "沒有區塊的for_with_index 結束"  
Proc 有個 call 方法,用以執行 Proc 物件內含的程序,上例中,實際上 yield(i) 該行,也可以改為 block.call(i)。

沒有留言:

張貼留言

網誌存檔

關於我自己

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