2008年4月7日 星期一

module_eval: dynamic code generation

Ruby的動態性真是超乎想像的炫,先看一個簡單的例子,code改自 “10 Things Every Java Programmer Should Know About Ruby”裡的“Item #7 Ruby is Way More Dynamic Than You Expect”

irb(main):001:0> puts 3.even? NoMethodError: undefined method `even?' for 3:Fixnum from (irb):1 irb(main):002:0> 3.class.module_eval "def even?() (self & 1).zero? end" irb(main):003:0> puts 3.even? false

第二行即時塞入數字class一個method,更炫的是,你甚至不用知道數字的 class 叫做 Integer,Item #7有太多神祕的功能,目前還不能理解它們的價值有多高。

再來這個例子很實用,但比較複雜,改自《Programming Ruby 2/e》p402。有時我們想將某些方法改用lazy initialization,這些值只會算一次。一般的做法,就是另設個private field,先檢查該field是否已設值,是的話就直接傳回,否的話先算再傳回,像singleton就是一個例子。

若有10個method想改怎麼辦?若改完後有特殊需求想改回來怎麼辦?若用C/C++、Java,自然是得改source code再重編譯code。先不提程式必須停止的影響,至少要反覆改code就不太方便。Ruby的話,可以寫兩個method,一個叫 once(),一個叫 unonce(),接著要做的,就是執行 once/unonce 即可,程式碼見這裡,說明如下:

  1. once接受任意數量的參數,參數是instance method name,once會將instance method加上一個cache,使得該method只會計算一次。
  2. 所有class都是Module的subclass,如此一來,所有新舊class都多了一個method once()。
  3. 對Ruby來說,class內的code等同於「執行」,所以 line 36 的 once :r 並不是什麼神奇的語法,而是在 class T 的內部,執行 method once 和參數 :r。
  4. line 40 顯示出,method r() 已被改變了,只會算一次 4. line 43 和 line 36 執行相反的行為。
  5. line 6 ~ 10 是核心程式,line 9 的 “[0]” 是用來處理 method 傳回多個值的情況,傳回多個值時,若 ‘=’ 左方只有一個變數,傳回值會自動變成陣列。

以上的例子說明了什麼呢?

這個例子揭露了Ruby強大的動態能力,原本我們想將method改成有cache或沒cache,得修改原始碼才行,若有10個method就要改10次,而透過module_eval的技巧,只要寫一次 once(),用一行code執行一次 once(),全部搞定!程式相當地有彈性,其它類似的雜事像 setVariable/getVariable也可以透過類似的技巧解決。Ruby內建的 attr_reader/attr_writer/attr_accessor 應該是類似的產物。

其它附帶的好處是程式可以在執行中即時修改既有的任何程式碼,而不用停止程式,事先寫好一些操作的話,除ruby interpreter更新外,程式不需要停止。

沒有留言:

張貼留言