What have you found for these years?

2007-03-27

Ruby 的 class 與 instance

不知道為何,最近不管是私事或公事似乎都很多。造成的結果就是很多事不知道該從何處
著手比較好。也許短時間內沒辦法寫什麼新東西也說不定,所以我又拿舊文章出來改了。
這次講的是 Ruby 的物件系統。不過呢,由於這篇原本是回答別人的問題,因此有些地方
講得不是很齊全,有些也講得比較偏。找到時間後,會再重新整理一次,現在就先這樣吧

編輯筆記:後面一段無關的刪去了,並補充了一小段。

==

在看 Ruby 的 class 與 instance 之前,先來看所謂 prototype-based language
是怎麼樣的東西,當然是舉大家最耳熟能詳的例子,ECMAScript(即 Javascript)

所謂 prototype-based 的意思是,沒有 class 的概念,所有的一切都是
instance, 產生東西一律使用 clone 的手法,從 prototype clone 出來。

在 ECMAScript 裡,要這樣操作 prototype:

=begin 例子

// 產生一個 function object, 會輸出「我是 ooo」
function say(){ print('I am ' + this) }

// 產生一個 function object, 拿這當 Duck 的 prototype
function Duck(){}

// 讓 Duck prototype 產生一個成員,也就是讓 say 變成他的 method
Duck.prototype.say = say;

// 定義 toString 讓 say 使用
Duck.prototype.toString = function(){ return 'Duck' }

// 假設現在有一個讓某東西說話的 function
function say_hello(who){ who.say() }

// 於是我們可以這樣呼叫 say_hello
say_hello(new Duck)

=end 例子

new Duck 會去尋找 Duck 的 prototype, 然後 clone 一份該 prototype 後傳回。
所以 say_hello 的 who 會是一份 Duck 的複製,執行 say 則會輸出:

I am Duck



ok, 回到 Ruby. 雖然說 Ruby 被分類成 class-based, 但事實上,
everything(ok, almost) is an object in Ruby, 就算是 class,
他其實本質上也是某個 instance, 是 Class 的 instance.

class A; end
a = A.new

a 是 A 的 instance, 所以 a 的 class 是 A.

a.class # A

A 是 Class 的 instance, 所以 A 的 class 是 Class

A.class # Class

其實,我覺得可以把這個 Class 視為某種 meta-class, 即 class 的 class,
如果我們要把 A 當嚴格 class 的話。但如果我們依然把 class 當 instance
看的話,當然,Class 本身其實也是一個 instance, 他是他自己的 instance.

Class.class # Class

有趣的是,這樣寫的話:

Class.object_id == Class.class.object_id

答案是:true.
Class.class 傳回來的,其實就是 Class, 也就是,他是他自己的 instance.
換句話說,其實 Class 是所有的 class 的 class.

A.kind_of? Class # true
Class.kind_of? Class # true
A.class # Class
Class.class # Class
A.class.object_id == Class.object_id # true



(btw, 其實更妙的是:

Module.kind_of? Class # true # Module 是 Class 的 instance
Class.kind_of? Module # true # Module 是 Class 的 superclass
Module.new.kind_of? Class # false # Module 的 instance 不是 Class



再加上 Object 會更複雜,可以試著畫畫看物件結構)

但是回想一下,一般我們是怎麼定義 class 的?

class A; end

其實,我個人會說這是一種 syntax sugar, 因為更合於 Ruby object system 的
定義方式,應該是這樣:

A = Class.new
A.send(:define_method,
:say_hello,
lambda{ puts "Hello from A's instance." })

a = A.new
a.say_hello # Hello from A's instance.



由於 define_method 是 private 的,所以要用 send 去呼叫。
其第一參數是你所要回應的 message symbol, 第二參數是 Proc/Method/Block 都可,
將會成為該 method 的 body.

也就是說,其實你寫

class A
def say_hello
puts "Hello from A's instance."
end
end



對於 A 來說,他是先從 Class 產生一個實體(instance),
然後將 A 這個「常數」指向那個實體,再對 A 呼叫 define_method,
把 say_hello 變成 symbol, 將 Block 變成該 method 的 body.

哪一個比較容易寫?當然是後者,畢竟那是大家都很習慣的模式,簡潔易懂。
所以我會說那種寫法其實在某種程度上來說,是 syntax sugar...
而 Ruby 其實也是用 prototype 建出其 class 體系,
這樣應該算是 prototype-based 還是 class-based, 看倌認為哩?

2007.02.08

補充:

有在寫 Ruby 的人,應該都知道 class 的名稱一定要大寫開頭。
事實上,這只是一種延伸規則,並不是基本的規則。依照:

a = Class.new

仍然是可以得到一個不是大寫開頭的 class. 大寫開頭的 identifier 在 Ruby 是一個
更簡單的概念:「常數」(constant)。也就是說,其實每一個 class name 都是一個
常數,一個 constant variable refer to a Class's instance, 一個指向 Class
實體的常數,就跟一般的變數沒兩樣。所以我們可以寫:

def make klass
klass.new
end

這個 functiona 接受一個參數,這個參數的 type 的必要條件是實作 new method,
而任何一個 Class instance 都符合這個條件。

make(Array).push(123)

把 Array 這個 instance 丟給 make 這個 function, 回傳就是一個 Array instance.
(global function 也值得探討,不過還是留待下次吧 :p)

那麼為什麼要讓 Class instance 成為一個常數?其實這是很顯而易見的,
如果你把原本定義好的 class 整個變成其他人了,例如:

Array = String

這樣意義在哪裡?以後寫 Array.new 產生的就不是原本的 array, 而是 string 了。
所以讓 class 成為一個 constant, 並要求其開頭字母是大寫,並非單純只是為了
命名習慣的問題,背後是還有牽扯到其他因素的。

我覺得 Ruby 有趣且厲害並迷人的地方就在於,他不單單只是有強大的威力在內,
還額外提供了許許多多方便使用的 syntax sugar, 使任何人都能快速上手,也能快速做
到任何他想做到的事情。而當你需要更強大的表達能力時,也能立刻拋棄 syntax sugar,
從最原始的 Ruby 道理開始寫起程式。也就是說,Ruby 提供了各種不同的寫程式角度,
大家都可以去找他喜愛的角度去使用。個人認為,Lisp 會不及 Ruby 方便,最主要的
差別就在這裡了。(當然,我並不熟 Lisp, 所以這句話恐怕是大有問題在…。)

2007.03.27 godfat 真常

延伸閱讀

0 retries:

Post a Comment

All texts are licensed under CC Attribution 3.0