Kedua konsep ini sangat mirip. Dalam bahasa OOP normal, kami melampirkan vtable (atau untuk interface: itable) ke setiap objek:
| this
v
+---+---+---+
| V | a | b | the object with fields a, b
+---+---+---+
|
v
+---+---+---+
| o | p | q | the vtable with method slots o(), p(), q()
+---+---+---+
Ini memungkinkan kami untuk memanggil metode yang mirip dengan this->vtable.p(this)
.
Di Haskell, tabel metode lebih seperti argumen tersembunyi implisit:
method :: Class a => a -> a -> Int
akan terlihat seperti fungsi C ++
template<typename A>
int method(Class<A>*, A*, A*)
di mana Class<A>
adalah instance dari typeclass Class
untuk tipe A
. Metode akan dipanggil seperti
typeclass_instance->p(value_ptr);
Instance terpisah dari nilai-nilai. Nilai masih mempertahankan tipe yang sebenarnya. Meskipun typeclasses memungkinkan polimorfisme, ini bukan subtipe polimorfisme. Itu membuatnya mustahil untuk membuat daftar nilai yang memuaskan a Class
. Misalnya dengan asumsi kita memiliki instance Class Int ...
dan instance Class String ...
, kita tidak dapat membuat tipe daftar yang heterogen seperti [Class]
yang memiliki nilai seperti [42, "foo"]
. (Ini dimungkinkan ketika Anda menggunakan ekstensi "tipe eksistensial", yang secara efektif beralih ke pendekatan Go).
Di Go, nilai tidak mengimplementasikan antarmuka tetap. Akibatnya tidak dapat memiliki pointer vtable. Sebagai gantinya, pointer ke tipe antarmuka diimplementasikan sebagai fat pointer yang mencakup satu pointer ke data, pointer lain ke itable:
`this` fat pointer
+---+---+
| | |
+---+---+
____/ \_________
v v
+---+---+---+ +---+---+
| o | p | q | | a | b | the data with
+---+---+---+ +---+---+ fields a, b
itable with method
slots o(), p(), q()
this.itable->p(this.data_ptr)
Itable digabungkan dengan data menjadi penunjuk gemuk saat Anda melakukan cast dari nilai biasa ke tipe antarmuka. Setelah Anda memiliki tipe antarmuka, tipe data yang sebenarnya telah menjadi tidak relevan. Bahkan, Anda tidak dapat mengakses bidang secara langsung tanpa melalui metode atau menurunkan antarmuka (yang mungkin gagal).
Pendekatan Go untuk pengiriman antarmuka dikenakan biaya: setiap pointer polimorfik dua kali lebih besar dari pointer normal. Juga, casting dari satu antarmuka ke antarmuka yang lain melibatkan menyalin pointer metode ke vtable baru. Tapi begitu kita sudah membangun itable, ini memungkinkan kita untuk mengirim panggilan metode ke banyak antarmuka dengan murah, sesuatu yang menderita dengan bahasa OOP tradisional. Di sini, m adalah jumlah metode dalam antarmuka target, dan b adalah jumlah kelas dasar:
- C ++ memang objek slicing atau perlu mengejar pointer virtual saat casting, tetapi kemudian memiliki akses vtable sederhana. O (1) atau O (b) biaya pengiriman, tetapi O (1) metode pengiriman.
- Java Hotspot VM tidak harus melakukan apa pun ketika upcasting, tetapi pada metode antarmuka lookup melakukan pencarian linear melalui semua itables yang diimplementasikan oleh kelas itu. O (1) upcasting, tetapi O (b) metode pengiriman.
- Python tidak harus melakukan apa pun ketika upcasting, tetapi menggunakan pencarian linear melalui daftar kelas dasar C3-linierisasi. O (1) upcasting, tetapi O (b²) metode pengiriman? Saya tidak yakin apa kompleksitas algoritmik C3.
- .NET CLR menggunakan pendekatan yang mirip dengan Hotspot tetapi menambahkan tingkat tipuan lain dalam upaya untuk mengoptimalkan penggunaan memori. O (1) upcasting, tetapi O (b) metode pengiriman.
Kompleksitas tipikal untuk pengiriman metode jauh lebih baik karena metode pencarian sering dapat di-cache, tetapi kompleksitas kasus terburuk cukup mengerikan.
Sebagai perbandingan, Go memiliki O (1) atau O (m) upcasting, dan O (1) metode pengiriman. Haskell tidak memiliki upcasting (membatasi tipe dengan kelas tipe adalah efek waktu kompilasi), dan metode pengiriman O (1).
[42, "foo"]
. Ini adalah contoh nyata.