Wie vermeiden Traits in Scala den "Diamantfehler"?

16

(Hinweis: Aus offensichtlichen Gründen habe ich im Titel 'error' anstelle von 'problem' verwendet.;)).

Ich habe ein paar grundlegende Lektüren zu Traits in Scala gemacht. Sie ähneln Interfaces in Java oder C #, ermöglichen jedoch die Standardimplementierung einer Methode.

Ich habe mich gefragt: Kann dies nicht den Fall des "Diamantenproblems" verursachen, weshalb viele Sprachen die Mehrfachvererbung überhaupt vermeiden?

Wenn ja, wie geht Scala damit um?

Aviv Cohn
quelle
Das Teilen Ihrer Forschung hilft allen . Sagen Sie uns, was Sie versucht haben und warum es nicht Ihren Bedürfnissen entsprach. Dies zeigt, dass Sie sich die Zeit genommen haben, um sich selbst zu helfen, es erspart uns, offensichtliche Antworten zu wiederholen, und vor allem hilft es Ihnen, eine spezifischere und relevantere Antwort zu erhalten. Siehe auch Wie man fragt
Mücke
2
@gnat: Dies ist eine konzeptionelle Frage, keine konkrete Problemfrage. Wenn er fragte: "Ich habe diese Klasse in Scala und es gibt mir Probleme, von denen ich denke, dass sie mit dem Diamantproblem zusammenhängen, wie kann ich das beheben?" dann wäre dein Kommentar angebracht, aber dann würde die Frage zu SO gehören. : P
Mason Wheeler
@MasonWheeler Ich habe auch einige grundlegende Lektüre über Scala gemacht. Und die erste Suche nach "Diamant" in dem, was ich gelesen habe, gab mir die Antwort: "Ein Merkmal verfügt über alle Funktionen des Java-Interface-Konstrukts. Auf Merkmalen können jedoch Methoden implementiert sein. Wenn Sie mit Ruby vertraut sind, sind die Merkmale ähnlich Sie können viele Merkmale in eine einzelne Klasse mischen. Merkmale können keine Konstruktorparameter annehmen, aber sie verhalten sich ansonsten wie Klassen. Dies gibt Ihnen die Möglichkeit, etwas zu haben, das sich der Mehrfachvererbung ohne das Diamantenproblem nähert. " Mangel an Mühe in dieser Frage fühlt sich ziemlich krass an
Mücke
7
Wenn Sie diese Aussage lesen, erfahren Sie nicht, WIE genau dies der Fall ist.
Michael Brown

Antworten:

21

Das Diamantproblem ist die Unfähigkeit, zu entscheiden, welche Implementierung der Methode gewählt werden soll. Scala löst dieses Problem, indem es definiert, welche Implementierung als Teil der Sprachspezifikationen ausgewählt werden soll ( lesen Sie den Teil über Scala in diesem Wikipedia-Artikel ).

Natürlich kann dieselbe Ordnungsdefinition auch bei der Klassenmehrfachvererbung verwendet werden. Warum also mit Merkmalen arbeiten?

Der Grund, warum IMO Konstruktoren sind. Konstruktoren haben einige Einschränkungen, die reguläre Methoden nicht haben - sie können nur einmal pro Objekt aufgerufen werden, sie müssen für jedes neue Objekt aufgerufen werden, und der Konstruktor einer untergeordneten Klasse muss den Konstruktor des übergeordneten Objekts als erste Anweisung aufrufen (die meisten Sprachen werden dies tun) tun Sie dies implizit für Sie, wenn Sie keine Parameter übergeben müssen.

Wenn B und C A und D B und C erben und die Konstruktoren von B und C den Konstruktor von A aufrufen, wird der Konstruktor von D den Konstruktor von A zweimal aufrufen. Definieren , welche Implementierungen zu wählen wie Scala hat mit Methoden werden hier nicht arbeiten , weil beide B und Konstrukteure des C genannt werden müssen.

Merkmale vermeiden dieses Problem, da sie keine Konstruktoren haben.

Idan Arye
quelle
1
Mit der C3-Linearisierung können die Konstruktoren einmal und nur einmal aufgerufen werden - so übernimmt Python die Mehrfachvererbung. Auf den ersten Blick ist die Linearisierung für den Diamanten D <B | C <A D -> B -> C -> A. Außerdem hat mir eine Google-Suche gezeigt, dass Scala-Merkmale veränderbare Variablen haben können, also gibt es mit Sicherheit eine Konstruktor irgendwo da drin? Aber wenn es Komposition unter der Haube verwendet (ich weiß nicht, habe nie Scala verwendet), ist es nicht schwer zu erkennen, dass B und C sich eine Instanz von A teilen könnten ...
Doval
... Merkmale scheinen nur eine sehr präzise Methode zu sein, um das gesamte Kesselbild auszudrücken, das für die Kombination von Schnittstellenvererbung und Komposition + Delegierung erforderlich ist.
Doval
@Doval Meine Erfahrung mit Konstruktoren in mehrfach vererbtem Python ist, dass sie ein königlicher Schmerz sind. Jeder Konstruktor kann nicht wissen, in welcher Reihenfolge er aufgerufen wird. Daher weiß er nicht, wie die Signatur seines übergeordneten Konstruktors lautet. Die übliche Lösung besteht darin, dass jeder Konstruktor eine Reihe von Schlüsselwortargumenten verwendet und die nicht verwendeten an seinen Superkonstruktor übergibt. Wenn Sie jedoch mit einer vorhandenen Klasse arbeiten müssen, die dieser Konvention nicht entspricht, können Sie nicht sicher davon erben es.
James_pic
Eine andere Frage ist, warum C ++ keine vernünftige Richtlinie für das Diamantenproblem gewählt hat.
Benutzer
20

Scala vermeidet das Diamantproblem durch sogenannte "Merkmalslinearisierung". Grundsätzlich wird die Methodenimplementierung in den Merkmalen nachgeschlagen, die Sie von rechts nach links erweitern. Einfaches Beispiel:

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

Allerdings enthält die Liste der Merkmale, nach denen es sucht, möglicherweise mehr als die, die Sie explizit angegeben haben, da sie möglicherweise andere Merkmale erweitern. Eine ausführliche Erklärung finden Sie hier: Merkmale als stapelbare Modifikationen und ein vollständigeres Beispiel für die Linearisierung hier: Warum nicht Mehrfachvererbung?

Ich glaube in anderen Programmiersprachen wird dieses Verhalten manchmal als "Methodenauflösungsreihenfolge" oder "MRO" bezeichnet.

lutzh
quelle