/ / Pourquoi avons-nous besoin de traits dans la scala? - scala, traits

Pourquoi avons-nous besoin de traits en scala? - scala, traits

Donc, j'essayais de créer un serveur finagle, de parler à la sentinelle (pas important), et je suis tombé sur un cas, où je devais hériter de deux Des classes (pas des traits) en même temps, appelons-les class SentryHandler extends Handler et class TwitterHandler extends Handleret supposons que je dois créer MyHandler, qui hérite des deux.

Après un moment de stupidité, quand je pensais que c'était impossible sans utiliser un "schéma de délégation" redouté, j'ai trouvé une solution:

trait SentryTrait extends SentryHandler
class MyHandler extends TwitterHandler with SentryTrait

Maintenant, cela m'a fait penser: quel est le but d'avoir la notion de «trait» d'être avec? Si l'idée était d'imposer que vous pouvez hériter de plusieurs traits mais d'une seule classe, il semble extrêmement facile de se déplacer. Cela ressemble un peu à class est censé être la ligne d'héritage "principale" (que vous "étendre une classe avec traits ", mais ce n'est pas vrai non plus: vous pouvez extend un trait avec (ou sans) un tas d'autres traits, et aucune classe du tout.

Vous ne pouvez pas instancier un trait, mais il en va de même pour une classe abstraite ...

La seule vraie différence à laquelle je peux penser est qu'un trait ne peut pas avoir de paramètres constructeur. Mais quelle est la signification de cela? Je veux dire, pourquoi pas? Quel serait le problème avec quelque chose comme ça?

class Foo(bar: String, baz: String) extends Bar(bar) with Baz(baz)

Réponses:

4 pour la réponse № 1

Votre solution (si j'ai bien compris) - ne fonctionne pas. Vous ne pouvez pas utiliser plusieurs classes héritées dans scala:

scala> class Handler
defined class Handler

scala> class SentryHandler extends Handler
defined class SentryHandler

scala> class TwitterHandler extends Handler
defined class TwitterHandler

scala> trait SentryTrait extends SentryHandler
defined trait SentryTrait

scala> class MyHandler extends TwitterHandler with SentryTrait
<console>:11: error: illegal inheritance; superclass TwitterHandler
is not a subclass of the superclass SentryHandler
of the mixin trait SentryTrait
class MyHandler extends TwitterHandler with SentryTrait

Quant à la question - pourquoi les traits, à mon sens, c'est parce que les traits sont empilables afin de résoudre le fameux problème de diamant

  trait Base { def x: Unit = () }
trait A extends Base { override def x: Unit = { println("A"); super.x}}
trait B extends Base { override def x: Unit = { println("B"); super.x}}

class T1 extends A with B {}
class T2 extends B with A {}

(new T1).x  // Outputs B then A
(new T2).x  // Outputs A then B

Même si le trait A super est Base (pour T1) il appelle B la mise en œuvre plutôt que Base. Cela est dû à linéarisation des traits

Donc pour les classes si vous étendez quelque chose - vous pouvez être sûr que cette base sera appelée ensuite. Mais ce n'est pas vrai pour les traits. Et c'est probablement pourquoi vous n'avez pas de paramètres de constructeur de trait


2 pour la réponse № 2

Le problème avec les constructeurs et l'état dansun trait (qui en fait alors une classe) est à héritage multiple. Bien que cela soit techniquement possible dans un langage hypothétique, c'est terrible pour la définition du langage et pour comprendre le code du programme. Le problème du diamant, mentionné dans d'autres réponses à cette question), appelle le constructeur de classe de base de plus haut niveau à deux reprises (le constructeur de A dans l'exemple ci-dessous).

Considérez ce code dans un langage de type Scala qui permet l'héritage multiple:

Class A(val x: Int)
class B extends A(1)
class C extends A(2)
class D extends B, C

Si l'état est inclus, vous devez avoir deuxcopies de la valeur x dans la classe A. Vous avez donc deux copies de la classe A (ou une copie et le problème du diamant - ainsi appelé en raison de la forme en losange du diagramme d'héritage UML).

Héritage multiple de diamant

Les premières versions du compilateur C ++ (appeléesC-Front) avait beaucoup de bogues avec cela et le compilateur ou le code compilé se bloquait souvent en les traitant. Les problèmes incluent si vous avez une référence à B ou C, comment déterminez-vous (le compilateur, en fait) le début de l'objet? Le compilateur doit savoir que pour convertir l'objet du type Base (dans l'image ci-dessous, ou A dans l'image ci-dessus) vers le type Descendant (D dans l'image ci-dessus).

Disposition de la mémoire d'héritage multiple

Mais cela s'applique-t-il aux traits? D'après ce que je comprends, les traits sont un moyen facile d'implémenter la composition en utilisant le modèle de délégation (je suppose que vous connaissez tous les modèles du GoF). Lorsque nous implémentons la délégation dans un autre langage (Java, C ++, C #), nous gardons une référence à l'autre objet et lui déléguons un message en appelant la méthode dans sa classe. Si les traits sont implémentés dans Scala en interne en gardant simplement une référence et en appelant sa méthode, alors les traits font exactement la même chose que Délégation. Alors, pourquoi ne peut-il pas avoir de constructeur? Je pense qu'il devrait pouvoir en avoir un sans violer son intention.


0 pour la réponse № 3

La seule vraie différence à laquelle je peux penser est qu'un trait ne peut pas avoir de paramètres constructeur. Mais quelle est la signification de cela? Je veux dire, pourquoi pas?

Considérer

trait A(val x: Int)
trait B extends A(1)
trait C extends A(2)
class D extends B with C

Qu'est-ce que doit (new D {}).x être? Remarque: il existe prévoit d'ajouter des paramètres de trait dans Scala 3, mais toujours avec des restrictions, de sorte que ce qui précède n'est pas autorisé.


0 pour la réponse № 4

La question devrait plutôt être: pourquoi avons-nous besoin de cours à Scala? Martin Odersky a déclaré que Scala pourrait s'en sortir avec seulement des traits. Nous aurions besoin d'ajouter des constructeurs aux traits, afin que des instances de traits puissent être construites. Ça va, Odersky a dit qu'il avait élaboré un algorithme de linéarisation pour les constructeurs de traits.

le réal le but est l'interopérabilité de la plate-forme.

Plusieurs plates-formes Scala a l'intention deintégrer avec (actuellement Java, anciennement .NET, peut-être à l'avenir Cocoa / Core Foundation / Swift / Objective-C) ont une notion distincte des classes, et il n'est pas toujours facile d'avoir une correspondance 1: 1 entre les traits Scala et la plate-forme Des classes. C'est différent, par exemple, des interfaces: il y a une correspondance triviale entre les interfaces de plate-forme et les traits Scala - un trait avec seulement des membres abstraits est isomorphe à une interface.

Classes, packages et null sont quelques exemples de fonctionnalités Scala dont l'objectif principal est l'intégration de la plateforme.

Les concepteurs de Scala s'efforcent de garder lelangue petite, simple et orthogonale. Mais Scala est également explicitement destiné à bien s'intégrer aux plates-formes existantes. En fait, même si le scala est une belle langue en soi, il était Plus précisément conçu en remplacement des principaux langages de plateforme (Java sur la plateforme Java, C # sur la plateforme .NET). Et pour ce faire, certains compromis doivent être faits:

  • Scala a des cours, même s'ils sont redondantsavec des traits (en supposant que nous ajoutons des constructeurs aux traits), car il est facile de mapper des classes Scala à des classes de plate-forme et presque impossible de mapper des traits à des classes de plate-forme. . (Pour chaque trait, il existe une interface qui contient l'API et une classe statique qui contient les méthodes. Pour chaque classe dans laquelle le trait est mélangé, une classe de redirecteur est générée qui transmet les appels de méthode aux méthodes de trait à la classe statique appartenant à ce trait.)
  • Scala a des packages, même s'ils sont redondants avec des objets. Les packages Scala peuvent être mappés de manière triviale aux packages Java et aux espaces de noms .NET. Les objets ne peuvent pas "t.
  • Les objets de package sont un moyen de surmonter certaines des limitations des packages, si nous n'avions pas de packages, nous n'aurions pas besoin d'objets de package.
  • Tapez Effacement. Il est parfaitement possible de conserver des types génériques lors de la compilation vers la JVM, par ex. vous pouvez les stocker dans des annotations. Mais les bibliothèques Java tierces verront leur type effacé de toute façon, et d'autres langages ne comprendront pas les annotations et ne traiteront pas non plus les types Scala, donc vous devez quand même gérer l'effacement de type, et si vous devez le faire de toute façon, alors pourquoi faire les deux?
  • null, bien sûr. Il n'est tout simplement pas possible de mapper automatiquement entre null et Option d'une manière sensée, lors de l'interopérabilité avec du code Java du monde réel. Tu dois avoir null à Scala, même si nous aurions préféré qu'il ne soit pas là.