隨筆.關於類別

PixelCat31415

OOP 已經不是什麼新概念了。我會的程式語言算一算可能有 C、C++、Python,勉強一點算上 Typescript,其中除了純 C 以外都有 class 這個關鍵字可以實做類別。我也曾經覺得我會用 class,至少用這些語言在語法層面上要實做一個最簡單的類別都不難。

某天我拿 Typescript 搞事情,發現我沒辦法回答一個好像很簡單的問題:類別是什麼?


我對類別的認識絕對是從 C++ 出發的,準確的說是從 C++ 的 struct 出發的。打競賽程式不時需要用到什麼資料結構像是線段樹或 BIT,我都喜歡用 struct 包起來,一方面保護命名空間,一方面喜歡把資料結構當成是一個黑盒子物件來用。C++ 的 struct 可以裝成員變數、方法,還可以繼承,跟 class 的界線淡到難以察覺1

而 struct 又是什麼?大一下修 DSA 寫純 C 有更深的體會:純 C 的 struct 沒有方法沒有任何物件要素,就只是把一些資料綁一起讓你一整捆帶著走。對 C 來說,struct 就只是一塊稍微大塊一點的記憶體,而你賦予這塊記憶體裡面每一個部份不同的意義。每一個變數都只是一塊記憶體,是變數的型別讓編譯器知道該如何對待這塊記憶體,讓每個位元組合出有價值的資料而不再只是一串 01 數字

這種想法也可以放在指標上。指標本質上就是一個數字,代表記憶體裡的一個位置,但沒辦法表達那個位置的資料究竟表示什麼。你可以用兩個數字精確標上地球上任意一個點的經緯度,卻沒辦法用兩個數字就展現那個位置的地貌和生態系。讓數字升級成資料的方法只能使用型別,讓編譯器和我(碼農)了解,這邊的 24 個 byte 是六個整數,那邊的 24 個 byte 卻代表一個裝著十萬個整數的 vector。也因此你當然可以隨便 cast 指標的型別,只是對錯的資料做同樣的事情,就像把一頭獅子當貓咪來擼,就不保證不會出事了。

所以我心目中的類別是一種型別—如果有一塊記憶體是這種型別,那我知道裡面的每一個部份代表什麼、還可以對這塊記憶體做一些特別的事情。


Python 呢?

1
2
3
4
5
6
7
8
9
>>> class C:
... def __init__(self):
... self.owo = 48763
...
>>> type(C)
<class 'type'>
>>> C = lambda x: x * 2
>>> type(C)
<class 'function'>

類別確實是一種「型別」,但是應該說他是一個變數,這個變數的型別是「型別 <class 'type'>」。好的可以理解,因為在 python 裡面連函數、類別都裝在變數裡面,這些變數一樣可以被重新裝進任意型別的資料,做類別或函數宣告跟造一個類別或函數存進這個變數是一樣意思。

C/C++ 裡面函數雖然也是一塊記憶體(裝著一段機器碼的記憶體),也可以用指標找到,但是他們跟一般變數有地位上的區隔。.text 區段在你編譯下去的那個瞬間就不能改了(通常情況下),那是編譯器的地盤。

無妨,我依然更加確信類別是一種型別。


直到我遇見 Typescript。

1
2
3
4
5
6
7
class C {
constructor(public owo: number) {}
}
console.log(C, typeof C);

const f = () => { return 48763; };
console.log(f, typeof f);

雖然 Typescript 的類別和函數也是裝在變數裡面,不過這次不能把別的東西裝進類別 C 了,因為 Typescript 不喜歡,很好我太喜歡這個設計了,比隔壁 python 合理多了。但是看看這個輸出。

1
2
3
$ npx ts-node main.ts
[class C] function
[Function: f] function

class 是 function!

我要用更大的版面來表達我的震驚與頓悟、瞠目結舌與不可思議、信念的崩塌與對自我的質疑,以及我寫下這整篇感言的唯一契機。

Typescript 覺得,類別是函數!

Typescript 覺得,或者說 Javascript 覺得,類別就是一個函數,這個函數會吐一個物件回來給你。其實沒什麼好震驚的,每個 Javascript 新手教程一定會有幾篇跟你解釋工廠模式和原形鍊,說 new 實際上只是幫你呼叫這個函數造一個物件還你;class 語法也只是語法糖,沒有類別這件事,原形和工廠才是 Javascript 所有物件的基礎。

但這讓型別和變數的分野一時變得有點模糊。寫這個類別的名字,到底是指一個型別,還是一個函數?

一開始寫 Typescript 用 class 覺得特別親切,跟寫別的語言沒什麼區別,無縫接軌;仔細一讀才發現哪裡是無縫接軌,根本就是曲折坎坷。可能學什麼都像這樣吧,一開始不會,後來覺得會了,然後又發現其實不會,在會和不會之間來回折返踉蹌往前走。

工廠和原形展現一種不同的哲學,甚至不存在其他語言裡類別的概念。說到底 Javascript 所有物件的型別都是 Object,跟其他語言不同,甚至 Python 的物件至少都有各自的型別,或許這也是為什麼在其他語言中見不到 Object.defineProperty() 這麼特別的語法的原因。


為什麼同樣的 class 這個字在不同語言要有不同的意義?也許原本 Javascript 設計的時候根本沒有 class 的概念,只是其他語言都有,所以把 Javascript 裡面類似的概念包裝成 class。就像在歐洲吃一桌台灣菜,很好吃,很有台灣味,但你還是嚐出了異鄉的味道。

想到這裡我只覺得設計每一種程式語言的人都是天才。


  1. 1.每次我想到 struct 和 class 都會回來複習 這篇 stackoverflow
On this page
隨筆.關於類別