Comment Traits in Scala évite-t-il «l'erreur diamant»?

16

(Remarque: j'ai utilisé «erreur» au lieu de «problème» dans le titre pour des raisons évidentes ..;)).

J'ai fait quelques lectures de base sur les traits à Scala. Ils sont similaires aux interfaces en Java ou C #, mais ils permettent l'implémentation par défaut d'une méthode.

Je me demandais: cela ne peut-il pas provoquer un cas du "problème du diamant", c'est pourquoi de nombreuses langues évitent en premier lieu l'héritage multiple?

Si oui, comment Scala gère-t-elle cela?

Aviv Cohn
la source
Partager vos recherches aide tout le monde . Dites-nous ce que vous avez essayé et pourquoi cela n'a pas répondu à vos besoins. Cela démontre que vous avez pris le temps d'essayer de vous aider, cela nous évite de réitérer des réponses évidentes et, surtout, cela vous aide à obtenir une réponse plus spécifique et pertinente. Voir aussi Comment demander
moucher
2
@gnat: c'est une question conceptuelle, pas une question de problème concret. S'il demandait "J'ai cette classe à Scala et cela me pose des problèmes qui, je pense, pourraient être liés au problème du diamant, comment puis-je le résoudre?" alors votre commentaire serait approprié, mais alors la question appartiendrait à SO. : P
Mason Wheeler
@MasonWheeler J'ai aussi fait quelques lectures de base sur Scala. Et la première recherche de "diamant" dans ce que j'ai lu m'a donné la réponse: "Un trait a toutes les caractéristiques de la construction de l'interface Java. Mais les traits peuvent avoir implémenté des méthodes dessus. Si vous êtes familier avec Ruby, les traits sont similaires aux mixins de Ruby. Vous pouvez mélanger de nombreux traits dans une seule classe. Les traits ne peuvent pas prendre les paramètres du constructeur, mais à part cela, ils se comportent comme des classes. Cela vous donne la possibilité d'avoir quelque chose qui approche l'héritage multiple sans le problème du diamant ". Le manque d'effort dans cette question semble plutôt flagrant
gnat
7
La lecture de cette déclaration ne vous dit pas COMMENT elle le fait.
Michael Brown

Réponses:

22

Le problème du diamant est l'incapacité de décider quelle mise en œuvre de la méthode choisir. Scala résout ce problème en définissant l'implémentation à choisir dans le cadre des spécifications du langage ( lire la partie sur Scala dans cet article Wikipedia ).

Bien sûr, la même définition d'ordre pourrait également être utilisée dans l'héritage multiple de classe, alors pourquoi s'embêter avec des traits?

La raison pour laquelle l'OMI est les constructeurs. Les constructeurs ont plusieurs limitations que les méthodes normales n'ont pas - ils ne peuvent être appelés qu'une seule fois par objet, ils doivent être appelés pour chaque nouvel objet, et le constructeur d'une classe enfant doit appeler le constructeur de son parent comme première instruction (la plupart des langages le faire implicitement pour vous si vous n'avez pas besoin de passer de paramètres).

Si B et C héritent de A et D héritent de B et C, et que les constructeurs de B et de C appellent le constructeur de A, alors le constructeur de D appellera le constructeur de A deux fois. Définir les implémentations à choisir comme Scala l'a fait avec les méthodes ne fonctionnera pas ici car les constructeurs B et C doivent être appelés.

Les traits évitent ce problème car ils n'ont pas de constructeurs.

Idan Arye
la source
1
Il est possible d'utiliser la linéarisation C3 pour appeler les constructeurs une seule fois - c'est ainsi que Python fait un héritage multiple. Du haut de ma tête, la linéarisation du diamant D <B | C <A est D -> B -> C -> A. De plus, une recherche sur Google m'a montré que les traits Scala peuvent avoir des variables mutables, donc il y a sûrement un constructeur quelque part là-dedans? Mais s'il utilise de la composition sous le capot (je ne sais pas, je n'ai jamais utilisé Scala), il n'est pas difficile de voir que B et C pourraient partager sur l'instance de A ...
Doval
... Les traits semblent être un moyen très concis d'exprimer tout le passe-partout qui va dans la combinaison de l'héritage d'interface et de la composition + délégation, qui est la bonne façon de réutiliser le comportement.
Doval
@Doval Mon expérience des constructeurs en Python hérité à plusieurs reprises est qu'ils sont une douleur royale. Chaque constructeur ne peut pas savoir dans quel ordre il sera appelé, il ne sait donc pas quelle est la signature de son constructeur parent. La solution habituelle consiste pour chaque constructeur à prendre un tas d'arguments de mots clés et à passer ceux qui ne sont pas utilisés à son super constructeur, mais si vous devez travailler avec une classe existante qui ne respecte pas cette convention, vous ne pouvez pas hériter de il.
James_pic
Une autre question est la suivante: pourquoi C ++ n'a-t-il pas choisi une politique saine pour le problème des diamants?
utilisateur
20

Scala évite le problème du diamant par quelque chose appelé "linéarisation des traits". Fondamentalement, il recherche l'implémentation de la méthode dans les traits que vous étendez de droite à gauche. Exemple simple:

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

Cela dit, la liste des traits qu'il recherche peut contenir plus que ceux que vous avez explicitement donnés, car ils pourraient étendre d'autres traits. Une explication détaillée est donnée ici: Les traits sous forme de modifications empilables et un exemple plus complet de la linéarisation ici: Pourquoi pas l'héritage multiple?

Je crois que dans d'autres langages de programmation, ce comportement est parfois appelé "ordre de résolution de méthode" ou "MRO".

Lutzh
la source