2006/07/08

Ruby Class Mechanism

今天凌晨終於看到 Ruby class 機制細節。

在 Ruby,萬事萬物皆物件,所以 class 亦為物件,記憶體佈局就是條「object → class (object) → superclass (object) → … → Object」的單向鏈結串列(singly-linked list)。這條 singly-linked list 所用的指標,實作上稱為 klass,當喚起一個 object(incl. class object)的函式,便從「這個 object 的 klass pointer」所指的 class object 開始搜尋,找到就喚起,未找到則上溯繼承鏈(at runtime),如此遞迴實施。如果整條串鏈都不含此函式,便喚起 Object#method_missing

Ruby 更可以插入 singleton methods 而產生所謂 singleton class(和 Singleton pattern 無關),即「為某一個 object 量身打造一個 class」。而既然 class 也是 object,於是就可以透過 singleton class 機制,造出 class instance variables / methods(但並非 class variables / methods)。而想喚起 class object 的 instance methods,為了一致,必須從它的 klass pointer 所指的 class object 開始搜尋,於是有了 metaclass,但這個 metaclass 和 Smalltalk 用以生成多個 class 的 metaclass 不同,只是為了一致而存在的 singleton class,在語言層面無法接觸 metaclass objects。

至於 Ruby 用來補償多繼承(multiple inheritance)的 mixin 功能,就只是把 mixin 插入那條(單)繼承鏈內。

這種設計當然相當彈性。Ruby 支援在任意時刻再次打開 class definition 加以塗改,而所有這個 class 的 objects 都會受影響,從上述機制很清楚能看到這點 ─ indirection 帶來的彈性。然而,喚起一個函式的 time complexity 是 O(C + M),其中 C 是繼承鏈的深度,M 是繼承鏈中所用的 mixins 個數,效能負擔很重(C++ virtual functions 只多了 vptr / vtbl 的間接性,就在效率上被人抨擊)。PickAxe(即《Programming Ruby, 2/e》之暱稱)在 Part III 最後說:

The important thing to remember about Ruby is that there isn't a big difference between "compile time" and "run time." It's all the same. You can add code to a running process. You can redefine methods on the fly, change their scope from public to private, and so on. You can even alter basic types, such as Class and Object.

Once you get used to this flexibility, it is hard to go back to a static language such as C++ or even to a half-static language such as Java.

But then, why would you want to do that?

"For efficiency, of course," I said. 說「compile time 和 run time 全然相同」,其實是省略了 compile time,把工作全移到 run time。這的確增加不少彈性,但也損失不少效率。"Efficiency (compile time) vs. flexibility (run time)",很經典的 trade-off 問題。