Différence entre l’inheritance des traits et les annotations de type auto

En Scala, j’ai vu les constructions

trait T extends S 

et

 trait T { this: S => 

utilisé pour obtenir des choses similaires (à savoir que les méthodes abstraites dans S doivent être définies avant qu’une instance puisse être créée). Quelle est la différence entre eux? Pourquoi utiliseriez-vous l’un sur l’autre?

J’utiliserais des auto-types pour la gestion des dépendances: ce trait nécessite qu’un autre trait soit mélangé. Et j’utiliserais l’inheritance pour affiner un autre trait ou une autre interface.

Juste à titre d’exemple:

 trait FooService trait FooRemoting { this : FooService => } trait FooPersistence { this : FooService => } object Services extends FooService with FooRemoting with FooPersistence 

Maintenant, si FooRemoting et FooPersistence auraient tous deux hérité de FooService, et si FooService a des membres et des méthodes, à quoi les services ressembleraient-ils?

Alors que pour l’inheritance, on aurait quelque chose comme:

 trait Iterator[T] { def hasNext : boolean def next : T } trait InfiniteIterator[T] extends Iterator[T] { def hasNext = true } 

Les annotations de type auto permettent d’exprimer des dépendances cycliques. Par exemple:

 trait A extends B trait B { self: A => } 

Ceci n’est pas possible avec l’inheritance simple.

Depuis que j’ai posé la question, je suis tombé sur ces articles:

Spiros Tzavellas parle d’utiliser un trait comme interface publique et le type auto comme un assistant qui doit être mélangé par la classe d’implémentation.

En conclusion, si nous voulons déplacer des implémentations de méthodes à l’intérieur de traits, nous risquons de polluer l’interface de ces traits avec des méthodes abstraites qui supportent la mise en œuvre des méthodes concrètes et sans rapport avec la responsabilité principale du trait. Une solution à ce problème consiste à déplacer ces méthodes abstraites dans d’autres traits et à composer les traits ensemble à l’aide d’annotations de type auto et d’inheritance multiple.

Par exemple:

 trait PublicInterface { this: HelperTrait => // Uses helperMethod } trait HelperTrait { def helperMethod = // ... } class ImplementationClass extends PublicInterface with HelperTrait 

Un tour de la Scala aborde l’utilisation des annotations de type auto avec des membres de type abstrait – sans doute, il n’est pas possible d’ extend un membre de type abstrait (?)

La réponse est “circularité”. Mais pas seulement.

L’annotation de type auto résout pour moi le problème fondamental de l’inheritance: ce dont vous héritez ne peut pas utiliser ce que vous êtes. Avec le type auto, tout devient facile.

Mon modèle est le suivant et peut être considéré comme un gâteau dégénéré:

 trait A { self: X => def a = reuseme} trait B { self: X => def b = a } class X extends A with B { def reuseme=null } 

Vous pouvez faire exploser votre classe avec plusieurs comportements qui peuvent être appelés de n’importe où dans l’assemblage, tout en restant proprement typescripts. Pas besoin de l’indirection douloureuse trop souvent (et à tort) identifiée avec le motif du gâteau.

La moitié (sinon la totalité) des frameworks Java DI compliqués des dix dernières années ont été consacrés à cela, bien sûr sans le typage. Les personnes utilisant encore JAVA dans ce domaine perdent clairement leur temps: “SCALA ouakbar”.

Bien que cela ne réponde pas à votre question, j’essayais de comprendre les annotations de type auto-analysé et je me suis retrouvé à parcourir les variations de votre question, en mettant l’accent sur l’utilisation d’annotations de type auto-déclarant.

Donc, voici une description d’un cas d’utilisation où les annotations de type auto-typées sont bien illustrées, à savoir quelque chose comme un cas type de «ceci» en tant que sous-type:

http://programming-scala.labs.oreilly.com/ch13.html#SelfTypeAnnotationsAndAbstractTypeMembers

espérant que cela serait utile à ceux qui se retrouvent sur cette question par hasard (et, comme moi, n’avait pas le temps de lire un livre de scala avant de commencer à explorer :-))

Je sais que cette question est ancienne mais je voudrais append quelques précisions et exemples.

Il y a trois différences principales entre l’inheritance des traits et les types de soi.

Sémantique

L’inheritance est l’une des relations avec le plus grand couplage du paradigme de l’object, si A étend B, cela signifie que A est un B.

Disons que nous avons le code suivant,

 trait Animal { def stop():Unit = println("stop moving") } class Dog extends Animal { def bark:Ssortingng = "Woof!" } val goodboy:Dog = new Dog goodboy.bark // Woof! 

Nous disons qu’un chien est un animal. Nous pouvons envoyer les messages bark et stop à goodboy car, étant un chien, il comprend les deux méthodes.

Maintenant, supposons que nous ayons un nouveau trait,

 trait Security { this: Animal => def lookout:Unit = { stop(); println("looking out!") } } 

Cette fois, la sécurité n’est PAS un animal, et c’est bien parce que ce serait sémantiquement incorrect si nous affirmons qu’une sécurité est un animal, ce sont des concepts différents, qui peuvent être utilisés ensemble.

Alors maintenant, nous pouvons créer un nouveau type de chien,

 val guardDog = new Dog with Security guardDog.lookout // stop moving // looking out! 

guardDog est un chien, un animal et la sécurité. Il comprend stop , bark et lookout car c’est un chien avec sécurité.

Mais que se passe-t-il si nous créons un nouveau chien comme celui-ci?

 val guardDog2:Dog = new Dog with Security guardDog2.lookout // no such method! 

guardDog2 est juste un chien, nous ne pouvons donc pas appeler la méthode de lookout . (okok, c’est un chien avec la sécurité, mais nous voyons juste un chien)

Dépendances cycliques

Les Self-types nous permettent de créer des dépendances cycliques entre les types.

 trait Patient { this: Reader => def isQuite:Boolean = isReading def isSlow:Boolean = true } trait Reader { this: Patient => def read():Unit = if(isSlow) println("Reading Slow...") else println("Reading Fast...") def isReading = true } val person = new Patient with Reader 

Le code suivant ne comstack pas.

 trait Patient extends Reader { /** code **/} trait Reader extends Patient { /** code **/ } 

Ce type de code est très commun dans l’dependency injection (modèle de gâteau).

Versatilité

Last but not least, qui utilise nos traits peut décider de l’ordre dans lequel ils sont utilisés, donc grâce à la linéarisation des caractères, le résultat final peut être différent, même si les traits utilisés sont les mêmes.

Avec l’inheritance normal, nous ne pouvons pas faire cela, les relations entre les traits et les classes sont fixes.

 trait Human { def isGoodForSports:Boolean } trait Programmer extends Human { def readStackOverflow():Unit = println("Reading...") override def isGoodForSports: Boolean = false } trait Sportsman extends Human { def play():Unit = println("Playing something") override def isGoodForSports: Boolean = true } val foo = new Programmer with Sportsman foo.isGoodForSports // true val bar = new Sportsman with Programmer bar.isGoodForSports // false 

J’espère que cela peut être utile.