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.

13voto

lcn Punkte 2069

Abschnitt 2.3 "Selftype Annotations" von Martin Oderskys ursprünglichem Scala-Artikel Skalierbare Komponentenabstraktionen erklärt den Zweck von selftype jenseits der mixin-Komposition sehr gut: eine alternative Möglichkeit zu bieten, eine Klasse mit einem abstrakten Typ zu assoziieren.

Das in dem Papier angeführte Beispiel sah wie folgt aus, und es scheint keine elegante Unterklassenentsprechung zu haben:

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;
  }
}

0 Stimmen

Für diejenigen, die sich fragen, warum die Unterklassenbildung dieses Problem nicht lösen kann, sei gesagt, dass Abschnitt 2.3 auch Folgendes besagt: "Jeder der Operanden einer Mixin-Komposition C_0 mit ... mit C_n, muss sich auf eine Klasse beziehen. Der Mixin-Kompositionsmechanismus erlaubt es nicht, dass C_i auf einen abstrakten Typ verweist. Diese Einschränkung macht es möglich, statisch auf Mehrdeutigkeiten und Überschreibungskonflikte an der Stelle zu prüfen, an der eine Klasse komponiert wird."

10voto

Rich Oliver Punkte 5833

Beginnen wir mit der Konjunkturabhängigkeit.

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

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

Die Modularität dieser Lösung ist jedoch nicht so groß, wie es auf den ersten Blick erscheinen mag, denn Sie können Self-Typen auch überschreiben:

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

Wenn Sie jedoch ein Element eines Selbsttyps überschreiben, verlieren Sie den Zugriff auf das ursprüngliche Element, auf das jedoch weiterhin über Super mittels Vererbung zugegriffen werden kann. Was also wirklich über die Verwendung von Vererbung gewonnen wird, ist:

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

Nun kann ich nicht behaupten, alle Feinheiten des Kuchenmusters zu verstehen, aber mir scheint, dass die Hauptmethode zur Durchsetzung der Modularität eher durch Komposition als durch Vererbung oder Selbsttypen erfolgt.

Die Vererbungsversion ist kürzer, aber der Hauptgrund, warum ich die Vererbung den Selbsttypen vorziehe, ist, dass ich es viel schwieriger finde, die Initialisierungsreihenfolge bei Selbsttypen richtig hinzubekommen. Allerdings gibt es einige Dinge, die man mit Selbsttypen machen kann, die man mit Vererbung nicht machen kann. Selbsttypen können einen Typ verwenden, während die Vererbung einen Trait oder eine Klasse erfordert, wie in:

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

Das können Sie auch tun:

trait TypeBuster
{ this: Int with String => }

Allerdings werden Sie ihn nie instanziieren können. Ich sehe keinen absoluten Grund dafür, dass man nicht von einem Typ erben kann, aber ich denke, dass es nützlich wäre, Pfadkonstruktorklassen und -traits zu haben, so wie wir Typkonstruktortraits/-klassen haben. Wie leider

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

Wir haben das hier:

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 }

Oder dies:

  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 }

Ein Punkt, der stärker beachtet werden sollte, ist, dass Traits Klassen erweitern können. Vielen Dank an David Maclver für den Hinweis darauf. Hier ist ein Beispiel aus meinem eigenen 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 erbt von der Schaukel Frame-Klasse, so dass sie als eigener Typ verwendet und dann am Ende (bei der Instanziierung) hinzugefügt werden kann. Allerdings, val geomR muss initialisiert werden, bevor es von vererbenden Merkmalen verwendet wird. Wir brauchen also eine Klasse, die eine vorherige Initialisierung von geomR . Die Klasse ScnVista kann dann von mehreren orthogonalen Merkmalen geerbt werden, die ihrerseits von diesen geerbt werden können. Die Verwendung mehrerer Typparameter (Generika) bietet eine alternative Form der Modularität.

7voto

Oleg Galako Punkte 1026
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
}

4voto

kikibobo Punkte 1147

Mit einem self-Typ können Sie angeben, welche Typen in einem Trait gemischt werden dürfen. Wenn Sie zum Beispiel einen Trait mit einem self-Typ haben Closeable dann weiß dieser Trait, dass die einzigen Dinge, die ihn einmischen dürfen, die Eigenschaft Closeable Schnittstelle.

3 Stimmen

@Blaisorblade: Ich frage mich, ob du die Antwort von kikibobo vielleicht falsch verstanden hast -- der Selbsttyp eines Traits erlaubt es in der Tat, die Typen einzuschränken, in die er gemischt werden kann, und das ist Teil seiner Nützlichkeit. Wenn wir zum Beispiel definieren trait A { self:B => ... } dann eine Erklärung X with A ist nur gültig, wenn X B erweitert. Ja, man kann sagen X with A with Q , wobei Q nicht über B hinausgeht, aber ich glaube, kikibobo wollte damit sagen, dass X so eingeschränkt ist. Oder habe ich etwas übersehen?

1 Stimmen

Danke, Sie haben Recht. Meine Stimme war gesperrt, aber zum Glück konnte ich die Antwort bearbeiten und dann meine Stimme ändern.

2voto

Petr Punkte 61399

Aktualisierung: Ein Hauptunterschied besteht darin, dass die Selbsttypen von folgenden Faktoren abhängen können mehrere Klassen (ich gebe zu, das ist ein bisschen ein Eckfall). Zum Beispiel können Sie haben

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;
}

Dies ermöglicht das Hinzufügen der Employee Mixin nur für alles, was eine Unterklasse von Person y Expense . Dies ist natürlich nur sinnvoll, wenn Expense erweitert Person oder andersherum. Der Punkt ist, dass die Verwendung von Selbsttypen Employee kann unabhängig von der Hierarchie der Klassen sein, von denen sie abhängt. Es kümmert sich nicht darum, was was erweitert - Wenn Sie die Hierarchie von Expense gegen Person müssen Sie nicht ändern Employee .

0 Stimmen

Employee muss keine Klasse sein, um von Person abzustammen. Traits können Klassen erweitern. Wenn die Eigenschaft Employee die Klasse Person erweitern würde, anstatt einen Selbsttyp zu verwenden, würde das Beispiel trotzdem funktionieren. Ich finde Ihr Beispiel interessant, aber es scheint keinen Anwendungsfall für Selbsttypen zu illustrieren.

0 Stimmen

@MorganCreighton Das stimmt, ich wusste nicht, dass Traits Klassen erweitern können. Ich werde darüber nachdenken, wenn ich ein besseres Beispiel finden kann.

1 Stimmen

Ja, das ist eine überraschende Sprachfunktion. Wenn die Eigenschaft "Employee" die Klasse "Person" erweitert, dann müsste jede Klasse, die letztlich in "Employee" aufgeht, auch "Person" erweitern. Diese Einschränkung bleibt jedoch bestehen, wenn Employee einen eigenen Typ verwendet, anstatt Person zu erweitern. Prost, Petr!

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