Bagaimana Traits in Scala menghindari "kesalahan intan"?

16

(Catatan: Saya menggunakan 'kesalahan' alih-alih 'masalah' dalam judul karena alasan yang jelas ..;)).

Saya melakukan bacaan dasar tentang Traits in Scala. Mereka mirip dengan Antarmuka di Java atau C #, tetapi mereka memungkinkan untuk implementasi metode standar.

Saya bertanya-tanya: tidak bisakah ini menyebabkan "masalah berlian", yang mengapa banyak bahasa menghindari pewarisan berganda?

Jika demikian, bagaimana Scala menangani ini?

Aviv Cohn
sumber
Berbagi penelitian Anda membantu semua orang . Beri tahu kami apa yang telah Anda coba dan mengapa itu tidak memenuhi kebutuhan Anda. Ini menunjukkan bahwa Anda telah meluangkan waktu untuk mencoba membantu diri sendiri, itu menyelamatkan kami dari mengulangi jawaban yang jelas, dan yang paling utama itu membantu Anda mendapatkan jawaban yang lebih spesifik dan relevan. Lihat juga Cara Meminta
nyamuk
2
@gnat: ini adalah pertanyaan konseptual, bukan pertanyaan masalah konkret. Jika dia bertanya "Saya memiliki kelas ini di Scala dan itu memberi saya masalah yang saya pikir mungkin terkait dengan Masalah Berlian, bagaimana cara memperbaikinya?" maka komentar Anda akan sesuai, tetapi kemudian pertanyaan itu akan menjadi milik SO. : P
Mason Wheeler
@MasonWheeler Saya melakukan beberapa bacaan dasar tentang Scala juga. Dan pencarian pertama untuk "berlian" dalam apa yang saya baca memberi saya jawaban: "Sebuah sifat memiliki semua fitur dari konstruksi antarmuka Java. Tetapi sifat dapat menerapkan metode pada mereka. Jika Anda terbiasa dengan Ruby, sifat-sifatnya serupa ke mixin Ruby. Anda dapat mencampur banyak sifat menjadi satu kelas. Sifat tidak dapat mengambil parameter konstruktor, tetapi selain itu mereka berperilaku seperti kelas. Ini memberi Anda kemampuan untuk memiliki sesuatu yang mendekati banyak pewarisan tanpa masalah berlian ". Kurangnya upaya dalam pertanyaan ini terasa agak mencolok
nyamuk
7
Membaca pernyataan itu tidak memberitahu Anda BAGAIMANA itu terjadi.
Michael Brown

Jawaban:

22

Masalah intan adalah ketidakmampuan untuk memutuskan implementasi metode mana yang harus dipilih. Scala memecahkan ini dengan mendefinisikan implementasi mana yang harus dipilih sebagai bagian dari spesifikasi bahasa ( baca bagian tentang Scala dalam artikel Wikipedia ini ).

Ofcourse, definisi urutan yang sama juga bisa digunakan di kelas multiple inheritance, jadi mengapa repot dengan sifat-sifat?

Alasan IMO adalah konstruktor. Konstruktor memiliki beberapa batasan yang tidak dimiliki metode reguler - mereka hanya dapat dipanggil satu kali per objek, mereka harus dipanggil untuk setiap objek baru, dan konstruktor kelas anak harus memanggil konstruktor induknya sebagai instruksi pertama (instruksi pertama (kebanyakan bahasa akan lakukan secara implisit untuk Anda jika Anda tidak perlu melewati parameter).

Jika B dan C mewarisi A dan D mewarisi B dan C, dan kedua konstruktor B dan C memanggil konstruktor A, maka konstruktor D akan memanggil konstruktor A dua kali. Mendefinisikan yang implementasi untuk memilih seperti Scala lakukan dengan metode tidak akan bekerja di sini karena kedua B dan konstruktor C harus dipanggil.

Sifat menghindari masalah ini karena mereka tidak memiliki konstruktor.

Idan Arye
sumber
1
Dimungkinkan untuk menggunakan linierisasi C3 untuk memanggil konstruktor satu kali dan hanya sekali - itulah bagaimana Python melakukan banyak pewarisan. Dari atas kepala saya linierisasi untuk D <B | C <Sebuah berlian adalah D -> B -> C -> A. Selain itu, pencarian Google telah menunjukkan kepada saya bahwa ciri-ciri Scala dapat memiliki variabel yang dapat berubah, jadi tentu saja ada konstruktor di suatu tempat di sana? Tetapi jika itu menggunakan komposisi di bawah tenda (saya tidak tahu, tidak pernah menggunakan Scala) tidak sulit untuk melihat bahwa B dan C dapat berbagi berdasarkan A ...
Doval
... Ciri-ciri hanya tampak seperti cara yang sangat ringkas untuk mengekspresikan semua boilerplate yang digunakan untuk menggabungkan pewarisan antarmuka dan komposisi + delegasi, yang merupakan cara yang tepat untuk menggunakan kembali perilaku.
Doval
@Doval Pengalaman saya tentang konstruktor dalam Python yang diwarisi berlipat ganda adalah bahwa itu adalah masalah kerajaan. Setiap konstruktor tidak dapat mengetahui urutan mana yang akan dipanggil, jadi tidak tahu apa tanda tangan konstruktor induknya. Solusi yang biasa adalah untuk setiap konstruktor untuk mengambil banyak argumen kata kunci, dan meneruskan yang tidak digunakan ke konstruktor supernya, tetapi jika Anda perlu bekerja dengan kelas yang ada yang tidak mengikuti konvensi itu, Anda tidak dapat dengan aman mewarisi dari Itu.
James_pic
Pertanyaan lain adalah mengapa C ++ tidak memilih kebijakan waras untuk masalah intan?
pengguna
20

Scala menghindari masalah intan dengan sesuatu yang disebut "linearisasi sifat". Pada dasarnya, ini mencari implementasi metode dalam sifat-sifat yang Anda perluas dari kanan ke kiri. Contoh sederhana:

trait Base {
   def op: String
}

trait Foo extends Base {
   override def op = "foo"
}

trait Bar extends Base {
   override def op = "bar"
}

class A extends Foo with Bar
class B extends Bar with Foo

(new A).op
// res0: String = bar

(new B).op
// res1: String = foo

Karena itu, daftar sifat yang dicari mungkin berisi lebih dari yang Anda berikan secara eksplisit, karena mereka mungkin memperluas sifat lain. Penjelasan terperinci diberikan di sini: Ciri-ciri sebagai modifikasi yang dapat ditumpuk dan contoh yang lebih lengkap dari linierisasi di sini: Mengapa tidak banyak pewarisan?

Saya percaya pada bahasa pemrograman lain perilaku ini kadang-kadang disebut sebagai "Metode resolusi urutan" atau "MRO".

Lutzh
sumber