Quelle est la différence entre les types et trait sous-classes?

Une auto-type pour un caractère A :

trait B
trait A { this: B => }

dit que A ne peut pas être mélangé dans une classe de béton qui ne s'étend pas aussi B .

par contre, le suivant:

trait B
trait A extends B

dit que "toute classe de mélange (concret ou abstrait) dans A sera également mélangée dans B " .

déclarations signifient la même chose? L'auto-type semble ne servir qu'à créer la possibilité d'une simple erreur de compilation.

Qu'est-ce que je rate?

360
demandé sur Jacek Laskowski 2010-01-02 11:07:53

11 réponses

il est principalement utilisé pour injection de dépendance , tel que dans le Dessin De Gâteau . Il existe un grand article couvrant de nombreuses formes différentes d'injection de dépendance dans le Scala, y compris le modèle de gâteau. Si Vous Google "Cake Pattern et Scala", vous obtiendrez de nombreux liens, y compris des présentations et des vidéos. Pour l'instant, voici un lien vers une autre question .

maintenant, quelle est la différence entre un self type et étendre un trait, c'est simple. Si vous dites B extends A , alors B est an A . Lorsque vous utilisez des self-types, B nécessite an A . Il y a deux exigences spécifiques qui sont créées avec des types de soi:

  1. si B est étendu, alors vous êtes requis pour mélanger-dans un A .
  2. Lorsqu'une classe de béton s'étend finalement / se mélange-dans ces caractéristiques, une certaine classe / trait doit mettre en œuvre A .

considérons les exemples suivants:

scala> trait User { def name: String }
defined trait User

scala> trait Tweeter {
     |   user: User =>
     |   def tweet(msg: String) = println(s"$name: $msg")
     | }
defined trait Tweeter

scala> trait Wrong extends Tweeter {
     |   def noCanDo = name
     | }
<console>:9: error: illegal inheritance;
 self-type Wrong does not conform to Tweeter's selftype Tweeter with User
       trait Wrong extends Tweeter {
                           ^
<console>:10: error: not found: value name
         def noCanDo = name
                       ^

si Tweeter était une sous-classe de User , il n'y aurait pas d'erreur. Dans le code ci-dessus, nous requis a User chaque fois que Tweeter est utilisé, mais un User n'a pas été fourni à Wrong , donc on a eu une erreur. Maintenant, avec le code ci-dessus toujours dans la portée, considérez:

scala> trait DummyUser extends User {
     |   override def name: String = "foo"
     | }
defined trait DummyUser

scala> trait Right extends Tweeter with User {
     |   val canDo = name
     | }
defined trait Right 

scala> trait RightAgain extends Tweeter with DummyUser {
     |   val canDo = name
     | }
defined trait RightAgain

avec Right , l'exigence de mélanger-dans un User est satisfaite. Toutefois, la deuxième condition mentionnée ci-dessus n'est pas remplie: la charge de la mise en œuvre de User demeure pour les classes/traits qui s'étendent Right .

avec RightAgain les deux exigences sont satisfaites. A User et un mise en œuvre de User sont fournis.

Pour plus de cas pratiques, veuillez consulter les liens au début de cette réponse! Mais, nous l'espérons maintenant que vous l'obtenez.

250
répondu Daniel C. Sobral 2017-11-05 05:44:32
Les types

vous permettent de définir des dépendances cycliques. Par exemple, vous pouvez obtenir ceci:

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

héritage utilisant extends ne permet pas cela. Essayez:

trait A extends B
trait B extends A
error:  illegal cyclic reference involving trait A

dans le livre Odersky, regarder la section 33.5 (création de tableur chapitre UI) où il est mentionné:

dans l'exemple de la feuille de calcul, le modèle de classe hérite de L'évaluateur et il accède ainsi à sa méthode d'évaluation. Aller dans l'autre sens, la classe Evaluator définit son propre type de modèle, comme ceci:

package org.stairwaybook.scells
trait Evaluator { this: Model => ...

Espérons que cette aide.

147
répondu Mushtaq Ahmed 2014-12-23 23:18:43

une autre différence est que les self-types peuvent spécifier des types non-class. Par exemple

trait Foo{
   this: { def close:Unit} => 
   ...
}

le self type ici est un type structurel. L'effet est de dire que tout ce qui se mélange dans Foo doit mettre en œuvre une méthode no-arg "close" Unité de retour. Cela permet des mélanges sûrs pour le canard-typing.

52
répondu Dave Griffith 2015-11-02 21:15:49

Section 2.3 " Selftype Annotations "of Martin Odersky's original Scala paper scalable Component Abstractions " explique en fait le but de selftype au-delà de la composition mixin très bien: fournir une autre façon d'associer une classe avec un type abstrait.

l'exemple donné dans le papier était comme suit, et il ne semble pas avoir un correspondant de sous-classe élégant:

abstract class Graph {
  type Node <: BaseNode;
  class BaseNode {
    self: Node =>
    def connectWith(n: Node): Edge =
      new Edge(self, n);
  }
  class Edge(from: Node, to: Node) {
    def source() = from;
    def target() = to;
  }
}

class LabeledGraph extends Graph {
  class Node(label: String) extends BaseNode {
    def getLabel: String = label;
    def self: Node = this;
  }
}
11
répondu lcn 2014-09-14 01:09:14

autre chose qui n'a pas été mentionnée: parce que les auto-types ne font pas partie de la hiérarchie de la classe requise, ils peuvent être exclus de l'appariement des motifs, surtout quand vous êtes exhaustivement appariés avec une hiérarchie scellée. C'est pratique lorsque vous voulez modéliser des comportements orthogonaux tels que:

sealed trait Person
trait Student extends Person
trait Teacher extends Person
trait Adult { this : Person => } // orthogonal to its condition

val p : Person = new Student {}
p match {
  case s : Student => println("a student")
  case t : Teacher => println("a teacher")
} // that's it we're exhaustive
11
répondu Bruno Bieth 2015-06-03 06:30:27

commençons par la dépendance cyclique.

trait A {
  selfA: B =>
  def fa: Int }

trait B {
  selfB: A =>
  def fb: String }

cependant, la modularité de cette solution n'est pas aussi grande qu'elle pourrait le paraître d'abord, parce que vous pouvez outrepasser les types d'auto comme cela:

trait A1 extends A {
  selfA1: B =>
  override def fb = "B's String" }
trait B1 extends B {
  selfB1: A =>
  override def fa = "A's String" }
val myObj = new A1 with B1

bien que, si vous outrepassez un membre d'un type personnel, vous perdez l'accès au membre original, qui peut encore être accédé par super en utilisant l'héritage. Donc, ce qui est vraiment gagné en utilisant l'héritage est:

trait AB {
  def fa: String
  def fb: String }
trait A1 extends AB
{ override def fa = "A's String" }        
trait B1 extends AB
{ override def fb = "B's String" }    
val myObj = new A1 with B1

maintenant je ne peux pas prétendre comprendre toutes les subtilités du motif de gâteau, mais il me frappe que la principale méthode d'imposer la modularité est par la composition plutôt que par l'héritage ou les types de soi.

la version d'héritage est plus courte, mais la principale raison pour laquelle je préfère l'héritage aux types de moi-même est que je trouve beaucoup plus difficile d'obtenir l'ordre d'initialisation correct avec les types de moi-même. Cependant, il y a certaines choses que vous pouvez faire avec les types de soi que tu ne peux pas faire avec l'héritage. Les types personnels peuvent utiliser un type alors que l'héritage nécessite un trait ou une classe comme dans:

trait Outer
{ type T1 }     
trait S1
{ selfS1: Outer#T1 => } //Not possible with inheritance.

Vous pouvez même le faire:

trait TypeBuster
{ this: Int with String => }

bien que vous ne pourrez jamais l'instancier. Je ne vois pas de raison absolue pour ne pas être en mesure d'hériter d'un type, mais je pense certainement qu'il serait utile d'avoir des classes et des traits du constructeur de chemins car nous avons des traits / classes du constructeur de types. Comme malheureusement

trait InnerA extends Outer#Inner //Doesn't compile

nous avons ceci:

trait Outer
{ trait Inner }
trait OuterA extends Outer
{ trait InnerA extends Inner }
trait OuterB extends Outer
{ trait InnerB extends Inner }
trait OuterFinal extends OuterA with OuterB
{ val myV = new InnerA with InnerB }

ou ceci:

  trait Outer
  { trait Inner }     
  trait InnerA
  {this: Outer#Inner =>}
  trait InnerB
  {this: Outer#Inner =>}
  trait OuterFinal extends Outer
  { val myVal = new InnerA with InnerB with Inner }

un point qui devrait être plus empathisé est que les traits peuvent étendre les classes. Merci à David Maclver d'avoir souligné cela. Voici un exemple de mon propre code:

class ScnBase extends Frame
abstract class ScnVista[GT <: GeomBase[_ <: TypesD]](geomRI: GT) extends ScnBase with DescripHolder[GT] )
{ val geomR = geomRI }    
trait EditScn[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]
trait ScnVistaCyl[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]

ScnBase hérite de la classe de cadre Swing , de sorte qu'il pourrait être utilisé comme un type autonome et ensuite mélangé à la fin (à l'instanciation). Cependant, val geomR doit être initialisé avant d'être utilisé en héritant des traits. Nous avons donc besoin d'une classe pour appliquer l'initialisation préalable de geomR . La classe ScnVista peut alors être héritée par de multiples traits orthogonaux qui peuvent eux-mêmes être hérités. L'utilisation de plusieurs paramètres de type (génériques) offre une autre forme de modularité.

9
répondu Rich Oliver 2013-11-03 11:41:57

TL;DR résumé des autres réponses:

  • les Types que vous étendez sont exposés aux types hérités, mais les self-types ne sont pas

    eg: class Cow { this: FourStomachs } vous permet d'utiliser des méthodes uniquement disponibles pour les ruminants, tels que digestGrass . Les Traits qui étendent la vache cependant n'auront pas de tels privilèges. D'autre part, class Cow extends FourStomachs exposera digestGrass à quiconque extends Cow .

  • auto-types de dépendances cycliques, en l'étendant à d'autres types de ne pas

8
répondu jazmit 2015-06-02 22:18:46
trait A { def x = 1 }
trait B extends A { override def x = super.x * 5 }
trait C1 extends B { override def x = 2 }
trait C2 extends A { this: B => override def x = 2}

// 1.
println((new C1 with B).x) // 2
println((new C2 with B).x) // 10

// 2.
trait X {
  type SomeA <: A
  trait Inner1 { this: SomeA => } // compiles ok
  trait Inner2 extends SomeA {} // doesn't compile
}
7
répondu Oleg Galako 2012-10-22 15:32:39

un self type vous permet de spécifier quels types sont autorisés à mélanger un trait. Par exemple , si vous avez un trait avec un auto-type Closeable , alors ce trait sait que les seules choses qui sont autorisées à le mélanger, doivent implémenter l'interface Closeable .

4
répondu kikibobo 2013-01-20 04:07:29

mise à jour: une des principales différences est que les auto-types peuvent dépendre des classes multiple (j'admets que c'est un cas de coin bit). Par exemple, vous pouvez avoir

class Person {
  //...
  def name: String = "...";
}

class Expense {
  def cost: Int = 123;
}

trait Employee {
  this: Person with Expense =>
  // ...

  def roomNo: Int;

  def officeLabel: String = name + "/" + roomNo;
}

cela permet d'ajouter le Employee mixin juste à tout ce qui est une sous-classe de Person et Expense . Bien sûr, cela n'a de sens que si Expense s'étend Person ou vice versa. Le point est que l'utilisation de self-types Employee peut être indépendant de la hiérarchie des classes dont il dépend. Il ne se soucie pas de ce qui étend quoi - Si vous changez la hiérarchie de Expense vs Person , vous ne devez pas modifier Employee .

1
répondu Petr Pudlák 2012-10-16 05:29:38

dans le premier cas, un sous-trait ou une sous-classe de B peut être mélangé à N'importe quel usage de A. Donc B peut être un trait abstrait.

0
répondu IttayD 2010-01-02 10:03:39