423 Stimmen

Was ist der Unterschied zwischen Selbsttypen und Eigenschaftsunterklassen?

Ein Selbsttyp für ein Merkmal A :

trait B
trait A { this: B => }

sagt, dass " A kann nicht in eine konkrete Klasse gemischt werden, die nicht auch die B " .

Auf der anderen Seite, die folgenden:

trait B
trait A extends B

sagt, dass "jede (konkrete oder abstrakte) Klasse, die in A wird auch in B gemischt" .

Bedeuten diese beiden Aussagen nicht das Gleiche? Der Selbsttyp scheint nur dazu zu dienen, die Möglichkeit eines einfachen Kompilierfehlers zu schaffen.

Was übersehe ich?

0 Stimmen

Ich interessiere mich eigentlich für die Unterschiede zwischen Selbsttypen und Unterklassen in Traits. Ich kenne einige der üblichen Verwendungszwecke für Selbsttypen; ich kann nur keinen Grund finden, warum sie nicht auf die gleiche Weise mit Subtypisierung durchgeführt werden sollten.

35 Stimmen

Man kann Typparameter innerhalb von Selbsttypen verwenden: trait A[Self] {this: Self => } ist legal, trait A[Self] extends Self ist es nicht.

4 Stimmen

Ein Selbsttyp kann auch eine Klasse sein, aber ein Trait kann nicht von einer Klasse erben.

298voto

Daniel C. Sobral Punkte 290004

Es wird vor allem verwendet für Injektion von Abhängigkeiten wie z. B. beim Tortenmuster. Es gibt eine großartiger Artikel über viele verschiedene Formen von Dependency Injection in Scala, einschließlich des Cake Patterns. Wenn Sie "Cake Pattern und Scala" googeln, erhalten Sie viele Links, einschließlich Präsentationen und Videos. Für den Moment ist hier ein Link zu weitere Frage .

Was nun der Unterschied zwischen einem Selbsttyp und der Erweiterung einer Eigenschaft ist, ist ganz einfach. Wenn Sie sagen B extends A dann B es eine A . Wenn Sie Selbsttypen verwenden, B erfordert eine A . Es gibt zwei spezifische Anforderungen, die mit Selbsttypen geschaffen werden:

  1. Si B erweitert wird, dann sind Sie erforderlich zum Einmischen eines A .
  2. Wenn eine konkrete Klasse schließlich diese Traits erweitert/vermischt, muss eine Klasse/ein Trait Folgendes implementieren A .

Betrachten Sie die folgenden Beispiele:

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 war eine Unterklasse von User würde es keinen Fehler geben. In dem obigen Code müssen wir erforderlich a User wenn Tweeter verwendet wird, jedoch eine User nicht zur Verfügung gestellt wurde Wrong und wir bekamen einen Fehler. Nun, mit dem Code oben immer noch in Reichweite, betrachten:

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

Mit Right das Erfordernis der Einmischung eines User erfüllt ist. Die zweite oben genannte Voraussetzung ist jedoch nicht erfüllt: Die Last der Umsetzung User bleibt für Klassen/Traits, die die Right .

Mit RightAgain beide Anforderungen erfüllt sind. A User und eine Implementierung von User sind vorgesehen.

Weitere praktische Anwendungsfälle finden Sie unter den Links am Anfang dieser Antwort! Aber jetzt haben Sie es hoffentlich verstanden.

3 Stimmen

Danke! Das Cake-Muster ist 90% dessen, was ich meine, warum ich über den Hype um die Selbsttypen spreche... dort habe ich das Thema zum ersten Mal gesehen. Das Beispiel von Jonas Boner ist großartig, weil es den Punkt meiner Frage unterstreicht. Wenn man die Self-Types in seinem Heizungsbeispiel in Subtraits umwandeln würde, was wäre dann der Unterschied (abgesehen von dem Fehler, den man bei der Definition der ComponentRegistry bekommt, wenn man nicht das Richtige hineinmischt?

30 Stimmen

@Dave: Du meinst wie trait WarmerComponentImpl extends SensorDeviceComponent with OnOffDeviceComponent ? Das würde dazu führen WarmerComponentImpl um diese Schnittstellen zu haben. Sie würden für alles zur Verfügung stehen, was die WarmerComponentImpl was eindeutig falsch ist, denn es ist no a SensorDeviceComponent noch ein OnOffDeviceComponent . Als Selbsttyp sind diese Abhängigkeiten verfügbar ausschließlich a WarmerComponentImpl . A List könnte als Array und andersherum. Aber sie sind einfach nicht dasselbe.

12 Stimmen

Danke Daniel. Das ist wahrscheinlich der wichtigste Unterschied, den ich gesucht habe. Das praktische Problem besteht darin, dass durch die Verwendung von Unterklassen Funktionalität in die Schnittstelle eindringt, die man nicht beabsichtigt. Das ist eine Folge der Verletzung der eher theoretischen "is-part-of-a"-Regel für Traits. Selbsttypen drücken eine "uses-a"-Beziehung zwischen Teilen aus.

164voto

Mushtaq Ahmed Punkte 6282

Selbsttypen ermöglichen es Ihnen, zyklische Abhängigkeiten zu definieren. Sie können zum Beispiel folgendes erreichen:

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

Vererbung mit extends lässt dies nicht zu. Versuchen Sie es:

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

Im Odersky-Buch finden Sie in Abschnitt 33.5 (Kapitel "Erstellen von Tabellenkalkulationen") einen Hinweis darauf:

Im Tabellenkalkulationsbeispiel erbt die Klasse Model von Evaluator und erhält somit Zugriff auf dessen Bewertungsmethode. Um den umgekehrten Weg zu gehen, definiert die Klasse Evaluator ihren eigenen Typ als Model definiert, etwa so:

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

Ich hoffe, das hilft.

3 Stimmen

Dieses Szenario hatte ich nicht in Betracht gezogen. Es ist das erste Beispiel von etwas, das ich gesehen habe, das nicht dasselbe ist wie ein Selbst-Typ, wie es mit einer Unterklasse ist. Es erscheint mir jedoch etwas kantig und, was noch wichtiger ist, es scheint eine schlechte Idee zu sein (ich gebe mir normalerweise große Mühe, KEINE zyklischen Abhängigkeiten zu definieren!) Halten Sie dies für den wichtigsten Unterschied?

4 Stimmen

Ich glaube schon. Ich sehe keinen anderen Grund, warum ich Selbsttypen der Erweiterungsklausel vorziehen würde. Self-Types sind langatmig, sie werden nicht vererbt (man muss also rituell Self-Types zu allen Subtypen hinzufügen) und man kann nur Member sehen, aber sie nicht überschreiben. Ich bin mir des Cake-Musters und der vielen Beiträge, in denen Selbsttypen für DI erwähnt werden, durchaus bewusst. Aber irgendwie bin ich nicht überzeugt. Ich hatte hier vor langer Zeit eine Beispielanwendung erstellt ( bitbucket.org/mushtaq/scala-di ). Sehen Sie sich speziell den Ordner /src/configs an. Ich habe DI erreicht, um komplexe Spring-Konfigurationen ohne Self-Types zu ersetzen.

0 Stimmen

Mushtaq, wir sind uns einig. Ich denke, dass Daniels Aussage, keine unbeabsichtigte Funktionalität freizugeben, wichtig ist, aber, wie Sie es ausdrücken, gibt es eine spiegelbildliche Ansicht dieser "Funktion"... dass man die Funktionalität nicht überschreiben oder in zukünftigen Unterklassen verwenden kann. Das sagt mir ziemlich klar, wann das eine dem anderen vorgezogen werden sollte. Ich werde Selbsttypen vermeiden, bis ich einen echten Bedarf sehe, d. h. wenn ich anfange, Objekte als Module zu verwenden, wie Daniel betont. Ich bin autowiring Abhängigkeiten mit impliziten Parametern und eine unkomplizierte Bootstrapper-Objekt. Ich mag die Einfachheit.

62voto

Dave Griffith Punkte 20265

Ein weiterer Unterschied besteht darin, dass Selbsttypen Nicht-Klassentypen angeben können. Zum Beispiel

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

Der Typ self ist hier ein Strukturtyp. Das bedeutet, dass alles, was sich in Foo mischt, eine "close"-Methode ohne Argumente implementieren muss, die unit zurückgibt. Dies ermöglicht sichere Mixins für Duck-Typing.

44 Stimmen

Eigentlich kann man Vererbung auch mit Strukturtypen verwenden: abstract class A extends {def close:Unit}

12 Stimmen

Ich denke, strukturelle Typisierung ist mit Reflexion, so verwenden Sie nur, wenn es keine andere Wahl gibt ...

0 Stimmen

@Adrian, ich glaube, Ihr Kommentar ist falsch. Die abstrakte Klasse A extends {def close:Unit}" ist einfach eine abstrakte Klasse mit der Oberklasse Object. Es ist nur die freizügige Syntax von Scala für unsinnige Ausdrücke. Sie können ` class X extends { def f = 1 }; new X().f` zum Beispiel

18voto

Bruno Bieth Punkte 2157

Eine weitere Sache, die noch nicht erwähnt wurde: Da Selbsttypen nicht Teil der Hierarchie der erforderlichen Klasse sind, können sie vom Musterabgleich ausgeschlossen werden, vor allem, wenn Sie einen umfassenden Abgleich mit einer versiegelten Hierarchie vornehmen. Dies ist praktisch, wenn Sie orthogonale Verhaltensweisen modellieren wollen, wie z. B.:

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

14voto

jazmit Punkte 4834

TL;DR Zusammenfassung der anderen Antworten:

  • Typen, die Sie erweitern, sind vererbten Typen ausgesetzt, Selbsttypen jedoch nicht

    z. B: class Cow { this: FourStomachs } können Sie Methoden anwenden, die nur für Wiederkäuer zur Verfügung stehen, wie z. B. digestGrass . Eigenschaften, die die Kuh verlängern, haben jedoch keine solchen Privilegien. Auf der anderen Seite, class Cow extends FourStomachs wird aufdecken digestGrass für jeden, der extends Cow .

  • Selbsttypen erlauben zyklische Abhängigkeiten, die Erweiterung anderer Typen ist nicht möglich.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X