W jaki sposób cechy w Scali unikają „błędu diamentu”?

16

(Uwaga: Użyłem „błędu” zamiast „problemu” w tytule z oczywistych powodów…;)).

Zrobiłem kilka podstawowych lektur na temat cech w Scali. Są one podobne do interfejsów w Javie lub C #, ale pozwalają na domyślną implementację metody.

Zastanawiałem się: czy nie może to spowodować „problemu z diamentem” i dlatego wiele języków w ogóle unika wielokrotnego dziedziczenia?

Jeśli tak, jak Scala sobie z tym poradzi?

Aviv Cohn
źródło
Udostępnianie badań pomaga wszystkim . Powiedz nam, co próbowałeś i dlaczego nie spełnia twoich potrzeb. To pokazuje, że poświęciłeś trochę czasu, aby spróbować sobie pomóc, oszczędza nam to powtarzania oczywistych odpowiedzi, a przede wszystkim pomaga uzyskać bardziej konkretną i odpowiednią odpowiedź. Zobacz także How to Ask
zgryz
2
@gnat: jest to pytanie koncepcyjne, a nie konkretne pytanie problemowe. Jeśli zapytał: „Mam tę klasę w Scali i to sprawia mi problemy, które, jak sądzę, mogą być związane z Diamentowym Problemem, jak to naprawić?” wtedy twój komentarz byłby odpowiedni, ale wtedy pytanie należałoby do SO. : P
Mason Wheeler,
@MasonWheeler Zrobiłem też podstawową lekturę na temat Scali. I pierwsze poszukiwanie „diamentu” w tym, co przeczytałem, dało mi odpowiedź: „Cecha ma wszystkie cechy konstrukcji interfejsu Java. Ale cechy mogą zaimplementować na nich metody. Jeśli znasz Ruby, cechy są podobne do miksów Ruby. Możesz mieszać wiele cech w jedną klasę. Cechy nie mogą przyjmować parametrów konstruktora, ale poza tym zachowują się jak klasy. Daje to możliwość posiadania czegoś, co zbliża się do wielokrotnego dziedziczenia bez problemu z diamentem ". Brak wysiłku w tym pytaniu wydaje się raczej rażący
komara
7
Czytanie tego oświadczenia nie mówi W JAKI sposób, że tak.
Michael Brown

Odpowiedzi:

22

Problem z diamentami polega na niemożności podjęcia decyzji, którą metodę wybrać. Scala rozwiązuje ten problem, określając, którą implementację wybrać jako część specyfikacji językowych ( przeczytaj część o Scali w tym artykule w Wikipedii ).

Oczywiście ta sama definicja porządku może być również stosowana w wielokrotnym dziedziczeniu klas, więc po co zawracać sobie głowę cechami?

Powodem, dla którego IMO jest konstruktorem. Konstruktory mają kilka ograniczeń, których nie mają zwykłe metody - można je wywoływać tylko raz na obiekt, należy je wywoływać dla każdego nowego obiektu, a konstruktor klasy potomnej musi wywołać konstruktor rodzica jako pierwszą instrukcję (większość języków będzie zrób to dla ciebie niejawnie, jeśli nie musisz przekazywać parametrów).

Jeśli B i C odziedziczą A i D, odziedziczą B i C, a oba konstruktory B i C wywołują konstruktor A, wówczas konstruktor D wywoła konstruktor A dwa razy. Definiowanie których implementacje wybrać jak Scala zrobił z metody nie będą działać tutaj, ponieważ oba muszą być nazywane B i konstruktorzy c za.

Cechy unikają tego problemu, ponieważ nie mają konstruktorów.

Idan Arye
źródło
1
Możliwe jest użycie linearyzacji C3 do wywołania konstruktorów raz i tylko raz - w ten sposób Python wykonuje wielokrotne dziedziczenie. Z czubka mojej głowy linearyzacja dla D <B | C <Diament to D -> B -> C -> A. Ponadto wyszukiwanie w Google wykazało, że cechy Scali mogą mieć zmienne zmienne, więc na pewno istnieje Konstruktor gdzieś tam? Ale jeśli używa kompozycji pod maską (nie wiem, nigdy nie używał Scali), nietrudno zauważyć, że B i C mogłyby dzielić się na przykład A ...
Doval
... Cechy wydają się po prostu bardzo zwięzłym sposobem wyrażenia wszystkich podstaw, które łączą dziedziczenie interfejsu i kompozycję + delegowanie, co jest właściwym sposobem na ponowne użycie zachowania.
Doval
@Doval Moje doświadczenie z konstruktorami w wielokrotnie odziedziczonym Pythonie polega na tym, że są królewskim bólem. Każdy konstruktor nie może wiedzieć, w jakiej kolejności zostanie wywołany, więc nie wie, jaki jest podpis jego konstruktora nadrzędnego. Zwykle rozwiązanie polega na tym, że każdy konstruktor bierze kilka argumentów słów kluczowych i przekazuje nieużywane argumenty do swojego superkonstruktora, ale jeśli musisz pracować z istniejącą klasą, która nie przestrzega tej konwencji, nie możesz bezpiecznie dziedziczyć po to.
James_pic
Kolejne pytanie brzmi: dlaczego C ++ nie wybrał zdrowej polityki dotyczącej problemu diamentów?
użytkownik
21

Scala unika problemu diamentów przez coś, co nazywa się „linearyzacją cechy”. Zasadniczo sprawdza implementację metody w cechach rozszerzanych od prawej do lewej. Prosty przykład:

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

To powiedziawszy, lista cech, które wyszukuje, może zawierać więcej niż te, które wyraźnie podałeś, ponieważ mogą one rozszerzyć inne cechy. Szczegółowe wyjaśnienie znajduje się tutaj: cechy jako modyfikacje, które można ustawiać jeden na drugim, i bardziej kompletny przykład linearyzacji: dlaczego nie wielokrotne dziedziczenie?

Wierzę, że w innych językach programowania takie zachowanie jest czasem określane jako „Kolejność rozwiązywania metod” lub „MRO”.

lutzh
źródło