UP | HOME

polymorphism 的執行期設計

在傳統的 ML 或是 Haskell 裡面,有一個型別表現的特別醜陋,就是 data type 表示的 disjoint。 這是因為無論如何,disjoint 就是會需要執行期時有要處理哪個分支的資訊。 因此語言中都設計有 constructor 這麼一個特殊的存在,其中每個 case 都有對應的執行期標籤。 舉例來說,

type bool = false | true

這裡 false 可能就是 0true 就是 1

type nat = z | s of nat

這裡 z 可能就是 0s 就是 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. 結語

不過我個人還是想繼續研究看看有沒有更細緻的解決方法,當然,進行更多的論文閱讀是必要的。 希望以後能夠對這個問題有更深入的見解。

Date: 2022-11-03 Thu 00:00
Author: Lîm Tsú-thuàn