Mengapa bahasa OOP statis arus utama yang kuat mencegah pewarisan primitif?

53

Mengapa ini OK dan sebagian besar diharapkan:

abstract type Shape
{
   abstract number Area();
}

concrete type Triangle : Shape
{
   concrete number Area()
   {
      //...
   }
}

... sementara ini tidak baik dan tidak ada yang mengeluh:

concrete type Name : string
{
}

concrete type Index : int
{
}

concrete type Quantity : int
{
}

Motivasi saya adalah memaksimalkan penggunaan sistem tipe untuk verifikasi ketepatan waktu kompilasi.

PS: ya, saya sudah baca ini dan pembungkusnya sudah beres-beres.

Sarang
sumber
1
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
maple_shaft
Saya memiliki motivasi yang serupa dalam pertanyaan ini , Anda mungkin menemukan itu menarik.
default.kramer
Saya akan menambahkan jawaban menegaskan "Anda tidak ingin warisan" ide, dan pembungkus yang sangat kuat, termasuk memberikan Anda mana casting implisit atau eksplisit (atau kegagalan) yang Anda inginkan, terutama dengan optimisations JIT menyarankan Anda akan tetap mendapatkan kinerja yang hampir sama, tetapi Anda telah menautkan ke jawaban itu :-) Saya hanya akan menambahkan, alangkah baiknya jika bahasa menambahkan fitur untuk mengurangi kode boilerplate yang diperlukan untuk meneruskan properti / metode, terutama jika hanya ada satu nilai.
Mark Hurd

Jawaban:

82

Saya menganggap Anda berpikir tentang bahasa seperti Java dan C #?

Dalam bahasa-bahasa itu primitif (seperti int) pada dasarnya adalah kompromi untuk kinerja. Mereka tidak mendukung semua fitur objek, tetapi mereka lebih cepat dan dengan lebih sedikit overhead.

Agar objek mendukung warisan, setiap instance perlu "tahu" pada saat runtime kelas mana itu merupakan instance. Jika tidak, metode yang diganti tidak dapat diselesaikan pada saat runtime. Untuk objek, ini berarti instance data disimpan dalam memori bersama dengan pointer ke objek kelas. Jika info tersebut juga harus disimpan bersama dengan nilai-nilai primitif, persyaratan memori akan membengkak. Nilai integer 16 bit akan membutuhkan 16 bit untuk nilai dan tambahan 32 atau 64 bit memori untuk pointer ke kelasnya.

Terlepas dari overhead memori, Anda juga diharapkan dapat mengesampingkan operasi umum pada primitif seperti operator aritmatika. Tanpa subtyping, operator seperti +dapat dikompilasi ke instruksi kode mesin sederhana. Jika itu bisa diganti, Anda harus menyelesaikan metode saat runtime, operasi yang jauh lebih mahal. (Anda mungkin tahu bahwa C # mendukung overloading operator - tetapi ini tidak sama. Overloading operator diselesaikan pada waktu kompilasi, sehingga tidak ada penalti runtime default.)

String bukan primitif tetapi mereka masih "istimewa" dalam bagaimana mereka diwakili dalam memori. Misalnya mereka "diinternir", yang berarti dua string literal yang sama dapat dioptimalkan ke referensi yang sama. Ini tidak akan mungkin (atau paling tidak banyak kurang efektif) jika instance string juga harus melacak kelas.

Apa yang Anda uraikan tentu akan bermanfaat, tetapi mendukungnya akan membutuhkan overhead kinerja untuk setiap penggunaan primitif dan string, bahkan ketika mereka tidak memanfaatkan warisan.

Bahasa Smalltalk tidak (saya percaya) memungkinkan subclassing bilangan bulat. Tetapi ketika Java dirancang, Smalltalk dianggap terlalu lambat, dan biaya overhead untuk menjadikan segala sesuatu sebagai objek dianggap sebagai salah satu alasan utama. Java mengorbankan keanggunan dan kemurnian konseptual untuk mendapatkan kinerja yang lebih baik.

JacquesB
sumber
12
@Den: stringdisegel karena dirancang untuk berperilaku tidak berubah. Jika seseorang dapat mewarisi dari string, itu mungkin untuk membuat string yang bisa berubah, yang akan membuatnya benar-benar rentan kesalahan. Banyak kode, termasuk kerangka. NET itu sendiri, bergantung pada string yang tidak memiliki efek samping. Lihat juga di sini, memberi tahu Anda hal yang sama: quora.com/Why-String-class-in-C-is-a-sealed-class
Doc Brown
5
@DocBrown. Ini juga alasannya Stringditandai finaldi Jawa juga.
Dev
47
"Ketika Java dirancang, Smalltalk dianggap terlalu lambat [...]. Java mengorbankan keanggunan dan kemurnian konseptual untuk mendapatkan kinerja yang lebih baik." - Ironisnya, tentu saja, Java tidak benar-benar mendapatkan kinerja itu sampai Sun membeli perusahaan Smalltalk untuk mendapatkan akses ke teknologi Smalltalk VM karena JVM milik Sun sendiri sangat lambat, dan merilis HotSpot JVM, sebuah Smalltalk VM yang sedikit dimodifikasi.
Jörg W Mittag
3
@underscore_d: Jawaban yang Anda tautkan dengan sangat eksplisit menyatakan bahwa C♯ tidak memiliki tipe primitif. Tentu, beberapa platform yang ada implementasi C♯ mungkin atau mungkin tidak memiliki tipe primitif, tetapi itu tidak berarti bahwa C♯ memiliki tipe primitif. Misalnya, ada implementasi Ruby untuk CLI, dan CLI memiliki tipe primitif, tetapi itu tidak berarti bahwa Ruby memiliki tipe primitif. Implementasi mungkin atau mungkin tidak memilih untuk mengimplementasikan tipe nilai dengan memetakannya ke tipe primitif platform tetapi itu adalah detail implementasi internal pribadi dan bukan bagian dari spesifikasi.
Jörg W Mittag
10
Ini semua tentang abstraksi. Kita harus menjernihkan pikiran kita, kalau tidak kita akan berakhir dengan omong kosong. Sebagai contoh: C♯ diimplementasikan pada .NET. .NET diimplementasikan pada Windows NT. Windows NT diimplementasikan pada x86. x86 diimplementasikan pada silikon dioksida. SiO₂ hanyalah pasir. Jadi, stringdalam C♯ hanya pasir? Tidak, tentu saja tidak, a stringdalam C♯ adalah apa yang menurut spesifikasi C♯ itu. Bagaimana penerapannya tidak relevan. Implementasi asli C♯ akan mengimplementasikan string sebagai byte array, implementasi ECMAScript akan memetakannya ke ECMAScript Strings, dll.
Jörg W Mittag
20

Apa yang diusulkan beberapa bahasa bukan subklas, tetapi subtipe . Misalnya, Ada memungkinkan Anda membuat jenis turunan atau subtipe . Bagian Ada Pemrograman / Jenis Sistem layak dibaca untuk memahami semua detail. Anda dapat membatasi rentang nilai, yang paling sering Anda inginkan:

 type Angle is range -10 .. 10;
 type Hours is range 0 .. 23; 

Anda dapat menggunakan kedua tipe ini sebagai Integer jika Anda mengonversinya secara eksplisit. Perhatikan juga bahwa Anda tidak dapat menggunakan satu di tempat yang lain, bahkan ketika rentang secara struktural setara (jenis diperiksa dengan nama).

 type Reference is Integer;
 type Count is Integer;

Tipe di atas tidak kompatibel, meskipun mereka mewakili kisaran nilai yang sama.

(Tetapi Anda dapat menggunakan Unchecked_Conversion; jangan beri tahu orang yang saya katakan sebelumnya)

coredump
sumber
2
Sebenarnya, saya pikir ini lebih tentang semantik. Menggunakan kuantitas di mana indeks diharapkan akan diharapkan menyebabkan kesalahan waktu kompilasi
Marjan Venema
@MarjanVenema Tidak, dan ini dilakukan dengan sengaja untuk menangkap kesalahan logika.
coredump
Maksud saya adalah bahwa tidak semua kasus di mana Anda ingin semantik, Anda akan memerlukan rentang. Anda kemudian akan memiliki type Index is -MAXINT..MAXINT;yang entah bagaimana tidak melakukan apa pun untuk saya karena semua bilangan bulat akan valid? Jadi kesalahan apa yang akan saya dapatkan jika melewati Angle ke Indeks jika semua yang diperiksa adalah rentang?
Marjan Venema
1
@ Marsjanen Dalam contoh kedua dia kedua jenis subtipe dari Integer. Namun, jika Anda mendeklarasikan fungsi yang menerima Count, Anda tidak dapat melewati Referensi karena pengecekan tipe didasarkan pada kesetaraan nama , yang merupakan kebalikan dari "semua yang diperiksa adalah rentang". Ini tidak terbatas pada bilangan bulat, Anda dapat menggunakan jenis atau catatan yang disebutkan. ( archive.adaic.com/standards/83rat/html/ratl-04-03.html )
coredump
1
@Marjan Salah satu contoh bagus mengapa jenis penandaan bisa sangat kuat dapat ditemukan dalam seri Eric Lippert tentang penerapan Zorg di OCaml . Melakukan hal ini memungkinkan kompiler untuk menangkap banyak bug - di sisi lain jika Anda mengizinkan untuk secara implisit mengonversi jenis ini sepertinya membuat fitur tidak berguna .. itu tidak masuk akal semantik untuk dapat menetapkan tipe PersonAge ke tipe PersonId saja karena mereka berdua kebetulan memiliki tipe dasar yang sama.
Voo
16

Saya pikir ini mungkin pertanyaan X / Y. Poin yang menonjol, dari pertanyaan ...

Motivasi saya adalah memaksimalkan penggunaan sistem tipe untuk verifikasi ketepatan waktu kompilasi.

... dan dari komentar Anda yang diuraikan:

Saya tidak ingin bisa mengganti satu dengan yang lain secara implisit.

Maafkan saya jika saya kehilangan sesuatu, tapi ... Jika ini tujuan Anda, lalu mengapa Anda berbicara tentang warisan? Pengganti tersirat adalah ... seperti ... semuanya. Anda tahu, Prinsip Pergantian Liskov?

Apa yang tampaknya Anda inginkan, pada kenyataannya, adalah konsep 'typedef kuat' - di mana sesuatu 'adalah' misalnya intdalam hal jangkauan dan representasi tetapi tidak dapat digantikan ke dalam konteks yang mengharapkan intdan sebaliknya. Saya sarankan mencari info tentang istilah ini dan apa pun bahasa yang Anda pilih menyebutnya. Sekali lagi, secara harfiah hal ini merupakan kebalikan dari warisan.

Dan bagi mereka yang mungkin tidak menyukai jawaban X / Y, saya pikir judulnya mungkin masih dapat dijawab dengan mengacu pada LSP. Tipe primitif adalah primitif karena mereka melakukan sesuatu yang sangat sederhana, dan hanya itu yang mereka lakukan . Membiarkan mereka diwarisi dan dengan demikian membuat efek tak terbatas yang mungkin terjadi akan menghasilkan kejutan besar pada pelanggaran LSP fatal dan paling fatal. Jika saya bisa dengan optimis menganggap Thales Pereira tidak keberatan saya mengutip komentar fenomenal ini :

Ada masalah yang ditambahkan bahwa jika seseorang dapat mewarisi dari Int, Anda akan memiliki kode tidak bersalah seperti "int x = y + 2" (di mana Y adalah kelas turunannya) yang sekarang menulis log ke Database, membuka URL dan entah bagaimana membangkitkan Elvis. Tipe primitif seharusnya aman dan dengan perilaku yang kurang lebih terjamin dan terdefinisi dengan baik.

Jika seseorang melihat tipe primitif, dalam bahasa yang waras, mereka dengan benar mengira itu akan selalu melakukan satu hal kecil, sangat baik, tanpa kejutan. Tipe primitif tidak memiliki deklarasi kelas yang tersedia yang menandakan apakah mereka mungkin diwariskan atau tidak dan metode mereka diganti. Jika ya, tentu akan sangat mengejutkan (dan benar-benar merusak kompatibilitas, tapi saya sadar itu jawaban mundur untuk 'mengapa X tidak dirancang dengan Y').

... meskipun, seperti yang ditunjukkan Mooing Duck dalam respons, bahasa yang memungkinkan overloading operator memungkinkan pengguna untuk mengacaukan diri mereka sendiri pada tingkat yang sama atau sama jika mereka benar-benar menginginkannya, sehingga meragukan apakah argumen terakhir ini berlaku. Dan saya akan berhenti meringkas komentar orang lain sekarang, heh.

underscore_d
sumber
4

Untuk memungkinkan pewarisan dengan pengiriman virtual (yang sering dianggap cukup diinginkan dalam desain aplikasi), seseorang perlu informasi jenis runtime. Untuk setiap objek, beberapa data mengenai jenis objek harus disimpan. Primitif, per definisi, tidak memiliki informasi ini.

Ada dua (dikelola, dijalankan pada VM) bahasa OOP utama yang fitur primitif: C # dan Java. Banyak bahasa lain tidak memiliki primitif di tempat pertama, atau menggunakan alasan yang sama untuk memungkinkan mereka / menggunakannya.

Primitif adalah kompromi untuk kinerja. Untuk setiap objek, Anda memerlukan ruang untuk header objeknya (Di Jawa, biasanya 2 * 8 byte pada VM 64-bit), ditambah bidangnya, ditambah padding akhirnya (Dalam Hotspot, setiap objek menempati sejumlah byte yang merupakan kelipatan dari 8). Jadi intobjek sebagai akan membutuhkan setidaknya 24 byte memori yang harus disimpan, bukan hanya 4 byte (di Jawa).

Dengan demikian, tipe primitif ditambahkan untuk meningkatkan kinerja. Mereka membuat banyak hal lebih mudah. Apa a + bartinya jika keduanya merupakan subtipe int? Beberapa jenis dispathcing harus ditambahkan untuk memilih tambahan yang benar. Ini berarti pengiriman virtual. Memiliki kemampuan untuk menggunakan opcode yang sangat sederhana untuk penambahan jauh, jauh lebih cepat, dan memungkinkan untuk optimasi waktu kompilasi.

Stringadalah kasus lain. Baik dalam Java dan C #, Stringadalah sebuah objek. Tapi di C # disegel, dan di Jawa final. Itu karena kedua pustaka standar Java dan C # memerlukan Strings agar tidak dapat diubah, dan mensubklasifikasikan mereka akan memecah kekekalan ini.

Dalam kasus Java, VM dapat (dan tidak) menginternir Strings dan "menyatukan" mereka, memungkinkan untuk kinerja yang lebih baik. Ini hanya berfungsi ketika String benar-benar tidak dapat diubah.

Plus, seseorang jarang perlu mensubklasifikasikan tipe primitif. Selama primitif tidak dapat disubklasifikasikan, ada banyak hal rapi yang dikatakan matematika tentang mereka. Misalnya, kita dapat memastikan bahwa penambahan bersifat komutatif dan asosiatif. Itulah sesuatu yang dikatakan definisi matematika bilangan bulat. Lebih jauh lagi, kita dapat dengan mudah melakukan invarian invariant melalui loop melalui induksi dalam banyak kasus. Jika kita mengizinkan subklasifikasi int, kita kehilangan alat yang diberikan matematika, karena kita tidak lagi dapat dijamin bahwa properti tertentu berlaku. Jadi, saya akan mengatakan kemampuan tidak dapat subclass tipe primitif sebenarnya hal yang baik. Lebih sedikit hal yang dapat dipecahkan seseorang, plus kompiler sering dapat membuktikan bahwa ia diizinkan melakukan optimasi tertentu.

Polygnome
sumber
1
Jawaban ini sangat sempit ... sempit. to allow inheritance, one needs runtime type information.Salah. For every object, some data regarding the type of the object has to be stored.Salah. There are two mainstream OOP languages that feature primitives: C# and Java.Apa, apakah C ++ bukan arus utama sekarang? Saya akan menggunakannya sebagai bantahan saya karena informasi jenis runtime adalah istilah C ++. Sama sekali tidak diperlukan kecuali menggunakan dynamic_castatau typeid. Dan bahkan jika RTTI aktif, pewarisan hanya menghabiskan ruang jika suatu kelas memiliki virtualmetode yang tabel per kelasnya harus ditunjukkan per instance
underscore_d
1
Warisan dalam C ++ bekerja jauh berbeda dengan bahasa yang dijalankan pada VM. pengiriman virtual memerlukan RTTI, sesuatu yang bukan bagian dari C ++. Warisan tanpa pengiriman virtual sangat terbatas dan saya bahkan tidak yakin apakah Anda harus membandingkannya dengan warisan dengan pengiriman virtual. Selain itu, gagasan tentang "objek" sangat berbeda dalam C ++ maka dalam C # atau Java. Anda benar, ada beberapa hal yang bisa saya sampaikan dengan lebih baik, tetapi dengan memasukkan semua poin yang cukup terlibat dengan cepat menyebabkan harus menulis buku tentang desain bahasa.
Polygnome
3
Juga, ini bukan kasus "pengiriman virtual memerlukan RTTI" di C ++. Sekali lagi, hanya dynamic_castdan typeinfomembutuhkan itu. Pengiriman virtual secara praktis diimplementasikan menggunakan pointer ke vtable untuk kelas konkret objek, sehingga membiarkan fungsi yang tepat dipanggil, tetapi tidak memerlukan detail jenis dan hubungan yang melekat dalam RTTI. Yang perlu diketahui oleh kompiler adalah apakah kelas objek polimorfik dan, jika demikian, apa vptr instance itu. Seseorang dapat dengan mudah mengkompilasi kelas yang sebenarnya dikirim -fno-rtti.
underscore_d
2
Sebenarnya sebaliknya, RTTI membutuhkan pengiriman virtual. Secara harfiah -C ++ tidak memungkinkan dynamic_castpada kelas tanpa pengiriman virtual. Alasan implementasi adalah bahwa RTTI umumnya diimplementasikan sebagai anggota tersembunyi dari suatu tabel.
MSalters
1
@MilesRout C ++ memiliki semua kebutuhan bahasa untuk OOP, setidaknya standar yang agak baru. Orang mungkin berpendapat bahwa standar C ++ yang lebih lama tidak memiliki beberapa hal yang diperlukan untuk bahasa OOP, tetapi bahkan itu adalah peregangan. C ++ bukan bahasa OOP tingkat tinggi , karena memungkinkan kontrol yang lebih langsung, tingkat rendah atas beberapa hal, tetapi tetap memungkinkan OOP. (Tingkat tinggi / tingkat rendah di sini dalam hal abstraksi , bahasa lain seperti yang dikelola abstrak lebih dari sistem jauh daripada C ++, maka abstraksi mereka lebih tinggi).
Polygnome
4

Dalam bahasa OOP statis statis yang kuat, sub-pengetikan dilihat terutama sebagai cara untuk memperluas jenis dan untuk menimpa metode jenis saat ini.

Untuk melakukannya, 'objek' berisi pointer ke tipenya. Ini adalah overhead: kode dalam metode yang menggunakan Shapeinstance harus terlebih dahulu mengakses informasi jenis instance itu, sebelum ia mengetahui Area()metode yang benar untuk dipanggil.

Primitif cenderung hanya memungkinkan operasi di atasnya yang dapat diterjemahkan ke dalam instruksi bahasa mesin tunggal dan tidak membawa informasi jenis apa pun dengannya. Membuat bilangan bulat lebih lambat sehingga seseorang dapat mensubklasnya menjadi tidak menarik untuk menghentikan bahasa apa pun yang menjadi arus utama.

Jadi jawaban untuk:

Mengapa bahasa OOP statis arus utama yang kuat mencegah pewarisan primitif?

Aku s:

  • Ada sedikit permintaan
  • Dan itu akan membuat bahasanya terlalu lambat
  • Subtyping terutama dilihat sebagai cara untuk memperluas jenis, daripada cara untuk mendapatkan pemeriksaan tipe statis yang lebih baik (ditentukan pengguna).

Namun, kami mulai mendapatkan bahasa yang memungkinkan pemeriksaan statis berdasarkan properti variabel selain 'tipe', misalnya F # memiliki "dimensi" dan "unit" sehingga Anda tidak dapat, misalnya, menambahkan panjang ke area .

Ada juga bahasa yang memungkinkan 'tipe yang ditentukan pengguna' yang tidak mengubah (atau menukar) apa yang dilakukan suatu tipe, tetapi hanya membantu dengan pemeriksaan tipe statis; lihat jawaban coredump.

Ian
sumber
Unit ukuran F # adalah fitur yang bagus, meskipun sayangnya salah nama. Juga hanya waktu kompilasi, jadi tidak sangat berguna misalnya ketika mengkonsumsi paket NuGet yang dikompilasi. Arah yang benar.
Den
Mungkin menarik untuk dicatat bahwa "dimensi" bukan "properti selain 'tipe'", itu hanya jenis yang lebih kaya daripada yang biasa Anda gunakan.
porglezomp
3

Saya tidak yakin apakah saya menghadap sesuatu di sini, tetapi jawabannya agak sederhana:

  1. Definisi primitif adalah: nilai primitif bukan objek, tipe primitif bukan tipe objek, primitif bukan bagian dari sistem objek.
  2. Warisan adalah fitur dari sistem objek.
  3. Ergo, primitif tidak dapat mengambil bagian dalam warisan.

Perhatikan bahwa hanya ada dua bahasa OOP statis yang kuat yang bahkan memiliki primitif, AFAIK: Java dan C ++. (Sebenarnya, saya bahkan tidak yakin tentang yang terakhir, saya tidak tahu banyak tentang C ++, dan apa yang saya temukan saat mencari membingungkan.)

Dalam C ++, primitif pada dasarnya adalah warisan yang diwarisi (pun intended) dari C. Jadi, mereka tidak mengambil bagian dalam sistem objek (dan dengan demikian warisan) karena C tidak memiliki sistem objek atau warisan.

Di Jawa, primitif adalah hasil dari upaya sesat untuk meningkatkan kinerja. Primitif juga merupakan satu-satunya tipe nilai dalam sistem, pada kenyataannya, tidak mungkin untuk menulis tipe nilai di Jawa, dan tidak mungkin bagi objek untuk menjadi tipe nilai. Jadi, terlepas dari fakta bahwa primitif tidak mengambil bagian dalam sistem objek dan dengan demikian gagasan "warisan" bahkan tidak masuk akal, bahkan jika Anda dapat mewarisinya, Anda tidak akan dapat mempertahankan " nilai-ness ". Ini berbeda dari misalnya C♯ yang memang memiliki tipe nilai struct, yang tetap merupakan objek.

Hal lain adalah bahwa tidak dapat mewarisi sebenarnya juga tidak unik untuk orang primitif. Dalam C♯, structs secara implisit mewarisi dari System.Objectdan dapat mengimplementasikan interfaces, tetapi mereka tidak dapat mewarisi dari atau diwarisi oleh classes atau structs. Juga, sealed classes tidak dapat diwarisi dari. Di Jawa, final classes tidak dapat diwarisi dari.

tl; dr :

Mengapa bahasa OOP statis arus utama yang kuat mencegah pewarisan primitif?

  1. primitif bukan bagian dari sistem objek (menurut definisi, jika mereka, mereka tidak akan primitif), gagasan warisan terikat pada sistem objek, warisan ergo primitif adalah kontradiksi dalam hal
  2. primitif tidak unik, banyak jenis lainnya tidak dapat diwarisi juga ( finalatau sealeddi Jawa atau C♯, structdi C♯, case classes di Scala)
Jörg W Mittag
sumber
3
Ehm ... Saya tahu ini diucapkan "C Sharp", tapi, ehm
Mr Lister
Saya pikir Anda cukup salah di sisi C ++. Itu sama sekali bukan bahasa OO murni. Metode kelas secara default tidak virtual, yang berarti mereka tidak mematuhi LSP. Misalnya std::stringbukan primitif, tetapi sangat berperilaku sebagai nilai lain. Semantik nilai semacam itu cukup umum, seluruh bagian STL dari C ++ mengasumsikannya.
MSalters
2
'Di Jawa, primitif adalah hasil dari upaya yang salah arah dalam meningkatkan kinerja.' Saya pikir Anda tidak tahu tentang besarnya hit kinerja menerapkan primitif sebagai jenis objek yang dapat diperluas pengguna. Keputusan itu di Jawa disengaja dan juga beralasan. Bayangkan saja harus mengalokasikan memori untuk setiap yang intAnda gunakan. Setiap alokasi mengambil urutan 100ns ditambah overhead pengumpulan sampah. Bandingkan dengan siklus CPU tunggal yang dikonsumsi dengan menambahkan dua primitif int. Kode java Anda akan merangkak jika perancang bahasa telah memutuskan sebaliknya.
cmaster
1
@ cmaster: Scala tidak memiliki primitif, dan kinerja numeriknya persis sama dengan Java. Karena, well, ia mengkompilasi bilangan bulat ke dalam JVM primitif int, sehingga mereka melakukan hal yang persis sama. (Scala-asli mengkompilasi mereka ke dalam register mesin primitif, Scala.js mengkompilasi mereka ke dalam ECMAScript Numbers primitif .) Ruby tidak memiliki primitif, tetapi YARV dan Rubinius mengkompilasi bilangan bulat menjadi bilangan bulat mesin primitif, JRuby mengkompilasi mereka ke dalam primitif JVM long. Hampir setiap implementasi Lisp, Smalltalk, atau Ruby menggunakan primitif di VM . Di situlah optimalisasi kinerja…
Jörg W Mittag
1
... termasuk: di kompiler, bukan bahasa.
Jörg W Mittag
2

Joshua Bloch dalam "Java Efektif" merekomendasikan desain secara eksplisit untuk warisan atau melarangnya. Kelas primitif tidak dirancang untuk pewarisan karena mereka dirancang untuk tidak berubah dan memungkinkan pewarisan dapat mengubah itu dalam subkelas, sehingga melanggar prinsip Liskov dan itu akan menjadi sumber banyak bug.

Ngomong-ngomong, mengapa ini solusi hacky? Anda harus benar-benar lebih suka komposisi daripada warisan. Jika alasannya adalah kinerja daripada yang ada benarnya dan jawaban untuk pertanyaan Anda adalah bahwa tidak mungkin untuk menempatkan semua fitur di Jawa karena butuh waktu untuk menganalisis semua aspek yang berbeda dalam menambahkan fitur. Misalnya Java tidak memiliki Generics sebelum 1.5.

Jika Anda memiliki banyak kesabaran maka Anda beruntung karena ada rencana untuk menambahkan kelas nilai ke Jawa yang akan memungkinkan Anda untuk membuat kelas nilai Anda yang akan membantu Anda meningkatkan kinerja dan pada saat yang sama akan memberi Anda lebih banyak fleksibilitas.

CodesInTheDark
sumber
2

Pada tingkat abstrak, Anda dapat memasukkan apa pun yang Anda inginkan dalam bahasa yang Anda desain.

Pada level implementasi, tidak terhindarkan bahwa beberapa dari hal-hal itu akan lebih mudah untuk diimplementasikan, beberapa akan rumit, beberapa dapat dibuat cepat, beberapa pasti akan lebih lambat, dan sebagainya. Untuk menjelaskan hal ini, desainer sering harus membuat keputusan dan kompromi yang sulit.

Pada tingkat implementasi, salah satu cara tercepat yang kami lakukan untuk mengakses suatu variabel adalah dengan mengetahui alamatnya dan memuat konten dari alamat itu. Ada instruksi khusus di sebagian besar CPU untuk memuat data dari alamat dan instruksi itu biasanya perlu tahu berapa byte yang mereka perlu muat (satu, dua, empat, delapan, dll) dan di mana harus meletakkan data yang mereka muat (register tunggal, register pasangan, register yang diperluas, memori lain, dll). Dengan mengetahui ukuran suatu variabel, kompiler dapat mengetahui instruksi mana yang harus dipancarkan untuk penggunaan variabel tersebut. Dengan tidak mengetahui ukuran variabel, kompiler harus menggunakan sesuatu yang lebih rumit dan mungkin lebih lambat.

Pada tingkat abstrak, titik subtipe adalah untuk dapat menggunakan contoh dari satu jenis di mana jenis yang sama atau lebih umum diharapkan. Dengan kata lain, kode dapat ditulis yang mengharapkan objek dari tipe tertentu atau apa pun yang lebih diturunkan, tanpa mengetahui sebelumnya apa sebenarnya ini. Dan jelas, karena lebih banyak tipe turunan dapat menambah lebih banyak anggota data, tipe turunan tidak harus memiliki persyaratan memori yang sama dengan tipe dasarnya.

Pada tingkat implementasi, tidak ada cara sederhana untuk variabel ukuran yang telah ditentukan untuk menyimpan instance ukuran yang tidak diketahui dan diakses dengan cara yang biasa Anda sebut efisien. Tetapi ada cara untuk memindahkan barang-barang sekitar sedikit dan menggunakan variabel untuk tidak menyimpan objek, tetapi untuk mengidentifikasi objek dan membiarkan objek itu disimpan di tempat lain. Cara itu adalah referensi (misalnya alamat memori) - tingkat tipuan ekstra yang memastikan bahwa variabel hanya perlu menyimpan beberapa jenis informasi ukuran tetap, selama kita dapat menemukan objek melalui informasi itu. Untuk mencapai itu, kita hanya perlu memuat alamat (ukuran tetap) dan kemudian kita dapat bekerja seperti biasa menggunakan offset dari objek yang kita tahu valid, bahkan jika objek itu memiliki lebih banyak data di offset yang tidak kita ketahui. Kita bisa melakukan itu karena kita tidak

Pada tingkat abstrak, metode ini memungkinkan Anda untuk menyimpan (referensi ke a) stringke dalam objectvariabel tanpa kehilangan informasi yang membuatnya menjadi string. Tidak masalah bagi semua tipe untuk bekerja seperti ini dan Anda mungkin juga mengatakan itu elegan dalam banyak hal.

Namun, pada tingkat implementasi, tingkat tipuan ekstra melibatkan lebih banyak instruksi dan pada kebanyakan arsitektur itu membuat setiap akses ke objek agak lambat. Anda dapat mengizinkan kompilator memeras lebih banyak kinerja dari suatu program jika Anda memasukkan dalam bahasa Anda beberapa tipe yang umum digunakan yang tidak memiliki tingkat tipuan ekstra (referensi). Tetapi dengan menghapus tingkat tipuan itu, kompiler tidak dapat lagi memungkinkan Anda untuk mengetikkan kembali dalam cara yang aman memori. Itu karena jika Anda menambahkan lebih banyak anggota data ke tipe Anda dan Anda menetapkan ke tipe yang lebih umum, setiap anggota data tambahan yang tidak sesuai dengan ruang yang dialokasikan untuk variabel target akan dipotong.

Theodoros Chatzigiannakis
sumber
1

Secara umum

Jika suatu kelas adalah abstrak (metafora: kotak dengan lubang), tidak apa-apa (bahkan diharuskan untuk memiliki sesuatu yang dapat digunakan!) Untuk "mengisi lubang", itu sebabnya kami mensubkelas kelas abstrak.

Jika suatu kelas adalah konkret (metafora: satu kotak penuh), tidak apa-apa untuk mengubah yang sudah ada karena jika penuh, itu penuh. Kami tidak punya ruang untuk menambahkan sesuatu lagi di dalam kotak, itu sebabnya kami tidak boleh subkelas kelas beton.

Dengan primitif

Primitif adalah kelas beton dengan desain. Mereka mewakili sesuatu yang terkenal, sepenuhnya pasti (saya belum pernah melihat tipe primitif dengan sesuatu yang abstrak, kalau tidak itu bukan primitif lagi) dan banyak digunakan melalui sistem. Mengizinkan untuk mensubklasifikasikan tipe primitif dan memberikan implementasi Anda sendiri kepada orang lain yang bergantung pada perilaku primitif yang dirancang dapat menyebabkan banyak efek samping dan kerusakan besar!

Tutul
sumber
Tautan ini merupakan opini desain yang menarik. Perlu lebih banyak pemikiran untuk saya.
Den
1

Biasanya warisan bukan semantik yang Anda inginkan, karena Anda tidak dapat mengganti tipe khusus Anda di mana pun primitif diharapkan. Meminjam dari contoh Anda, a Quantity + Indextidak masuk akal secara semantik, jadi hubungan pewarisan adalah hubungan yang salah.

Namun, beberapa bahasa memiliki konsep tipe nilai yang mengungkapkan jenis hubungan yang Anda gambarkan. Scala adalah salah satu contohnya. Tipe nilai menggunakan primitif sebagai representasi yang mendasarinya, tetapi memiliki identitas kelas yang berbeda dan operasi di luar. Itu memiliki efek memperluas tipe primitif, tetapi lebih merupakan komposisi daripada hubungan warisan.

Karl Bielefeldt
sumber