What have you found for these years?

2012-02-23

prototype-based 與 class-based

日前在 g+ 上聊到了 prototype 相關的議題,然後允諾要稍微講一下
目前對於 prototype 的一些想法。結果一被說期待,就想說不能講錯,
稍微多細想並驗證了一下。結果不試則已,一試發現自己原本的想法
果真不太對。這樣一來,似乎應該比較正式地完整地說一次?

不過最近實在太累了,剛剛還在想搞不好我會又發呆到天亮。不如乾脆
就隨意把目前的想法稍微說說即可,不需要太完整或正式。

*

簡單地說,prototype 跟 class 是一體兩面。在 class 也是 first-class
value 且足夠動態的語言中,這兩者幾乎可以說是沒有差別。或是說,
prototype 其實就是 class 本身。差別在於,prototype 產生 instance
的方式其實是產生另一個 subclass, 而不是真的產生一個 instance
(in terms of class-based languages)

後面再講更仔細一點。

當然如果在靜態語言,或是 class 不是 first-class value 的語言中,
就會有很顯然的差距了,因為 prototype 在定義上就是 first-class
value, 而 class 在許多語言中並不是 first-class value.

另一個 g+ thread 中,可以連到〈prototype的聯想〉然後可以連到
以 C 語言實做 Javascript 的 prototype 特性〉然後可以連到
Ruby 的 class 與 instance

老實講我覺得還滿巧的 XD 因為最後那篇確實是我最早的出發點。2007-03!
天啊,居然是五年前?打到這裡又停頓了一下,不過我想我還是不要離題好了。

現在去看那篇,我覺得我那時的範例雖然不能說有錯,但模糊了焦點,沒把
prototype 跟一般 class 最重要的差距表現出來。或是說,反而是拿 prototype
去遷就一般的 class. 這樣沒有表達出 prototype 真正的威力。

我不確定 JavaScript 有沒有這種能力。由於我不熟 JavaScript, 也對 JavaScript
完完全全失去興致了,所以我就不實驗 JavaScript 了。我們直接來看 Io 的程式。

在 Io 裡,所有的 object 都是 prototype, 也可以說所有的 prototype 都是 class.
產生物件的方式或是定義 class (prototype) 的方式都完全是靠 clone. 我們先來
變出隻貓:
Cat := Object clone
Cat name := ""
Cat meow := method("#{name}: meow~" interpolate println)
貓可以有名字,會喵喵叫。喵喵叫時頭上會浮現他自己的名字。 Io 的語法是,
空一格表示 method call. 因此 Object clone 在 C-like 語言是 Object.clone.
method call 可以一直串起來,本身是 left-associative, 因此:
"#{name}: meow~" interpolate println

是被 parse 成:
(("#{name}: meow~" interpolate) println)

換成 C-like 語法就是:
"#{name}: meow~".interpolate().println()

跟 Smalltalk 一樣,什麼東西都要透過物件去操作。其實 Ruby 也是,不過最外層
有個 main object 使得 Ruby 也可以寫得看起來很不物件。

接下來我們生出一隻叫 Alice 的貓:
Alice := Cat clone
Alice name := "Alice"
Alice meow
應該沒什麼特別的,就是從 Cat clone 出來,然後指定名字,叫牠叫叫看。
輸出結果也一如預期地是:Alice: meow~
接著定義 Bob, 跟 Alice 是一樣的,只是名字不同。沒有性別。
Bob   := Cat clone
Bob   name := "Bob"
Bob   meow
接著當然是變種 Carol 出場的時候啦。Carol 不是貓,Carol 是 Bob:
Carol := Bob clone
Carol name := "Carol"
Carol meow
在這邊我們可以看到一個複製順序:Object -> Cat -> Bob -> Carol
由於 Bob 和 Carol 都還沒做什麼特別的事,所以目前牠們沒什麼差別。
接下來 Bob 學會了怎麼跳:
Bob jump := method("#{name}: jump~" interpolate println)
也沒什麼特別的,跟喵喵叫一樣,只是聲音不太一樣而已。到這裡,我們應該
就猜得到 Bob 會跳,Carol 由於也是 Bob 的「一種」,牠也會跳。
不過 Alice 就不會跳了,因為牠不是 Bob, 牠只是一隻貓。
Bob jump
Carol jump
// Alice jump // no jump for Alice
聰明的讀者
相信大家已經看出來了,其實這跟 class 的繼承沒什麼兩樣,只是我們把 class
也當 instance, 也當一般的 object 操作而已。上面的複製順序,其實應該反過來
當作繼承順序才對:Object <- Cat <- Bob <- Carol

我得承認我只是翻了一下 Io 怎麼寫,然後就驟下這個結論。不過我覺得這正應該是
prototype 方便有用之處:沒有必要去區分 class 與 instance, 寫就對了。不需要
分析我們現在需要哪些「分類」,不斷從現有的分類繼續分下去即可。

這其實帶來混亂沒有錯。想像在一個每一個物件都是一個 class 的世界... 只是這些
各自不同的 class, 其實很可能都是相同的。例如,如果上面的程式裡,沒有偷偷教
Bob 跳躍的話,Carol 跟 Alice 其實也是一樣的。而就算這樣做了,Bob 跟 Carol
基本上還是一樣的。

在 Ruby 裡,其實可以做完全一樣的事。(至少以我粗粗粗粗淺淺淺淺的 Io 認識)
Cat = Class.new(Object)
我們 "clone" 一份 Class 出來當 Cat, 以 Object 當其 superclass.
其實那個 Object 可以省略,因為在 Ruby 裡,本來所有的東西都是繼承
自 Object. 這邊寫出來,只是為了和 Io 和下面的程式看起來更一致。
那行其實也完全等同於:
class Cat
end
定義 name writer 和喵喵叫。
def Cat.name= name
  @name = name
end
def Cat.meow
  puts "#{@name}: meow~"
end
接著我們可以看到,要從 Cat 再 clone 出來,還是要透過 class, 畢竟 Ruby
還是 class-based, 而 prototype 本身其實就可以看成是一種 class.
Alice = Class.new(Cat)
Alice.name = "Alice"
Alice.meow

Bob   = Class.new(Cat)
Bob  .name = "Bob"
Bob  .meow
同樣,第一行等同於在 Ruby 裡寫:
class Alice < Cat
end
最後我們來到了 Carol 和教導 Bob 怎麼跳躍:
Carol = Class.new(Bob)
Carol.name = "Carol"
Carol.meow

def Bob.jump
  puts "#{@name}: jump~"
end
Bob.jump

Carol.jump

# Alice.jump # no jump for Alice

當然,這些或許可以說是從 Ruby 的觀點來看,畢竟對於其他語言,
可能是沒辦法做到類似的事情,因此 class 與 prototype 就有其他的區別。
不過我想以最極端的情況來看,差不多就是這樣了。prototype 的重點其實
是在能夠動態調整 class, 而不是事先被定義好的靜態 class.

這可能有點類似 dynamic typing 跟 static typing 的差異吧。而動態到了
一個程度的時候,其實 prototype 就是 class 了。如果我們有一個方法可以
限制 Io object 不能再繼續 clone, 比方說拿掉那個 object 本身的 clone
method, 那麼我們就可以把它視為一種 instance. 這樣就只是一體兩面了。

class 觀點的好處在於,讓我們能夠用一致的方法去「分類」。prototype
則是讓我們省去「分類」的煩惱,反正什麼東西都自成一類就對了。
分類的好處則讓我們比較容易懂程式在運作的時的樣貌。

分類上要再更極端一點的話,則是不只區分 type (class) 與 value (instance),
再區分 kind (metaclass). 說起來大概就是 value is classified by type,
type is classified by kind. Ruby 其實只有兩層,沒有 kind 的層次。
Class 的 class 還是 Class, 硬生生壓扁成同一層。Ruby 的 metaclass,
也就是 singleton_class, 其實也是 Class 的一種... prototype 則是把
一切壓扁成只有一層。可以說是只有 value 層,也可以說是只有 class 層。
Agda 這種應該可以有無限層的就... 不是我能夠理解的範圍了 Orz
Set1? Set2?? Set3???


我在細想這些東西時,其實是覺得 prototype 本身是殘廢的。不過試了 Io
才發覺兩者其實可以看成同一件事。如果在其它語言中 class 不是 first-class
value, 或是 prototype 沒有像 Io 這樣容易把所有的東西都視為同樣的,
那大概就會有很多不同的考量了。到這種時候,大概就只能針對語言一個個
分開來談了。


完整程式可以在 sandbox/mix/prototype 裡面找到。


p.s. 前幾天在 blog 上裝了 hljs, 現在顯示上色的程式太方便了,
只要定義 class="io" 就能顯示 Io 的 syntax, 果然這樣做才是正途。
之前用 pygment 效果是很好,但是用起來就很麻煩。我很閒的話,
看看要不要把之前的都改成用 hljs 上色,然後也許拿掉之前無用的 css...

p.s. 不過我發現我搞錯了,它其實不認得 io 是什麼,反而是給我挑 ruby 去上色...
大概是因為 string interpolation 的語法一樣的關係吧。結果最後那段居然給我挑成
perl, 因為 no 和 for 在 perl 裡看起來都有特別的意思。我本來是想手動選擇 delphi,
scala 之類的,至少讓 // 看起來像是註解。不過它不理我 :( 不知道是 bug 還是怎樣。

0 retries:

Post a Comment

All texts are licensed under CC Attribution 3.0