polymorphism 的執行期設計
在傳統的 ML 或是 Haskell 裡面,有一個型別表現的特別醜陋,就是 data type 表示的 disjoint。 這是因為無論如何,disjoint 就是會需要執行期時有要處理哪個分支的資訊。 因此語言中都設計有 constructor 這麼一個特殊的存在,其中每個 case 都有對應的執行期標籤。 舉例來說,
type bool = false | true
這裡 false
可能就是 0
而 true
就是 1
。
type nat = z | s of nat
這裡 z
可能就是 0
而 s
就是 1
並且接有一個 tuple 放 nat
。
1. 理論上的優雅解法
要是這種 disjoint 語義想要完美對應 category theory 的
coproduct,那就需要引入真正的 union type。 要注意到這跟 C 語言的 union
略有不同,C 語言事實上仰賴了開發者完全知道自己在做什麼作為前提,
要求開發者對 union 正確的操作。要是要求 C
語言一樣要能處理不同型別的輸入的話,一樣要放置一個標籤欄位才能解決。
union type 在型別上是很優美,沒有 多餘 的標籤,在 typed/racket
裡面可以記成 (U Integer Boolean String)
之類的。
但這個方案遠非毫無缺點,之所以可以這麼做,事實上是因為
racket(以及早期的諸多 functional languages)採用了 universal object
編碼。
2. Universal object
這個名詞,其實就是指對每一個執行期的物件,都把型別囊括在內。
這樣一來當然就不需要另外給標籤,因為每個物件本來就都有標籤了。
可想而知,這個方法缺點就是針對需要緊密排列的運用場景效果不佳。 所以
racket 要是需要操作 vector float
這種資料,再怎麼編譯都還是會慢 C
語言一線, 主因就是沒有用最緊密的方式排列資料(多了標籤),相較於 C
沒有利用到當代硬體特性。 但反過來說就沒有 ad-hoc
的編譯難題,因為本來就是需要 runtime 解決的問題就推到 runtime 解決。
3. 靜態類型檢查語言的改進
那麼可想而知,後來為了適當壓縮物件,ML 跟 Haskell 都走上混合的解決方案。 針對小物件(通常是指小於等於該平台的一個 word 的資料型別)就會盡可能不要包起來,反之就採用 universal object, 在實務上表現也確實不錯。
4. 結語
不過我個人還是想繼續研究看看有沒有更細緻的解決方法,當然,進行更多的論文閱讀是必要的。 希望以後能夠對這個問題有更深入的見解。