Як риси Scala уникають "алмазної помилки"?


16

(Примітка. Я використав "помилку" замість "проблема" в заголовку з очевидних причин ..;)).

Я почитав основні риси в Scala. Вони схожі на інтерфейси Java або C #, але вони дозволяють реалізувати метод за замовчуванням.

Мені було цікаво: чи не може це спричинити випадок "алмазної проблеми", саме тому багато мов у першу чергу уникають багаторазового успадкування?

Якщо так, то як Scala це справляється?


Обмін дослідженнями допомагає всім . Розкажіть, що ви пробували і чому це не відповідало вашим потребам. Це свідчить про те, що ви знайшли час, щоб спробувати допомогти собі, це позбавляє нас від повторення очевидних відповідей, а найбільше це допомагає вам отримати більш конкретну та релевантну відповідь. Також дивіться Як запитувати
gnat

2
@gnat: це концептуальне питання, а не конкретне проблемне питання. Якщо він запитував "У мене цей клас у Scala, і це створює мені проблеми, які, на мою думку, можуть бути пов'язані з проблемою Diamond, як я це виправити?" тоді ваш коментар був би доречним, але тоді питання належало б ТАК. : P
Мейсон Уілер

@MasonWheeler Я теж кілька читав на Scala. І перший пошук "алмазу" в прочитаному нами дав відповідь: "У ознаки є всі особливості конструкції інтерфейсу Java. Але в них можуть бути реалізовані методи. Якщо ви знайомі з Ruby, риси схожі до комбінацій Рубі. Ви можете змішати багато рис в одному класі. Риси не можуть приймати конструкторські параметри, окрім того, що вони поводяться як класи. Це дає можливість мати щось, що наближається до багатократного успадкування без проблеми з алмазами ". Брак зусиль у цьому питанні відчуває себе досить кричущим
гнат

7
Читання цього твердження не говорить вам, ЯК саме це робить.
Майкл Браун

Відповіді:


22

Проблема алмазу полягає в неможливості вирішити, яку реалізацію методу вибрати. Scala вирішує це, визначаючи, яку реалізацію вибрати як частину мовних специфікацій ( читайте частину про Scala в цій статті Вікіпедії ).

Зрозуміло, те саме визначення порядку також може бути використане у множинному успадкуванні класу, так навіщо турбуватися з рисами?

Причиною ІМО є конструктори. У конструкторів є кілька обмежень, яких у звичайних методів немає - їх можна викликати лише один раз на кожен об'єкт, їх потрібно викликати для кожного нового об'єкта, а конструктор дочірнього класу повинен викликати конструктор батьків, як це перша інструкція (більшість мов будуть зробіть це неявно для вас, якщо вам не потрібно передавати параметри).

Якщо B і C успадковують A, D успадковують B і C, і обидва конструктори B і C називають конструктор A, то конструктор D викличе конструктор A двічі. Визначення, які реалізації вибрати, як Скала робив із методами, тут не працюватиме, оскільки обидва повинні називатися B - х і конструкторами C в.

Риси уникають цієї проблеми, оскільки вони не мають конструкторів.


1
Можна використовувати лінеаризацію C3 для виклику конструкторів один раз і лише один раз - саме так Python робить багатократне успадкування. Лінеаризація для D <B | C <алмаза D -> B -> C -> А. вгорі моєї голови виявила, що пошук Google показав мені, що риси Scala можуть мати змінні змінні, тому, безумовно, є конструктор десь там? Але якщо він використовує композицію під кришкою (я не знаю, ніколи не використовував Scala), не важко помітити, що B і C можуть поділитися на екземпляр A ...
Doval

... Риси просто здаються дуже стислим способом висловити всю панель котлів, що входить у поєднання спадкування інтерфейсу та складу + делегування, що є правильним способом повторного використання поведінки.
Довал

@Doval Мій досвід конструкторів у множині успадкованих Python полягає в тому, що вони королівські болі. Кожен конструктор не може знати, в якому порядку він буде викликаний, тому не знає, що таке підпис його батьківського конструктора. Звичайне рішення полягає в тому, щоб кожен конструктор взяв купу аргументів ключових слів і передавав невикористані на його супер конструктор, але якщо вам потрібно працювати з існуючим класом, який не дотримується цієї конвенції, ви не можете безпечно успадкувати це.
James_pic

Інше питання полягає в тому, що чому C ++ не обрав розумну політику щодо алмазної проблеми?
користувач

21

Скала уникає алмазної проблеми чимось так званим "лінеаризацією ознак". В основному, він шукає реалізацію методу в рисах, які ви поширюєте справа наліво. Простий приклад:

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

Сказавши це, список ознак, які він шукає, може містити більше тих, які ви явно надали, оскільки вони можуть поширювати інші риси. Тут подано детальне пояснення: Риси як модифікації, які можна скласти та більш повний приклад лінеаризації тут: чому б не множинне успадкування?

Я вірю, що в інших мовах програмування таку поведінку іноді називають "порядком вирішення методу" або "MRO".

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.