Qual è la differenza tra i tipi di auto e l'ereditarietà dei tratti in Scala?

9

Quando eseguito su Google, vengono visualizzate molte risposte per questo argomento. Tuttavia, non mi sembra che nessuno di loro faccia un buon lavoro per illustrare la differenza tra queste due caratteristiche. Quindi mi piacerebbe provare ancora una volta, in particolare ...

Che cosa si può fare con i self-types e non con l'ereditarietà e viceversa?

Per me, dovrebbe esserci una differenza fisica quantificabile tra i due, altrimenti sono nominalmente diversi.

Se il tratto A estende B o i tipi di sé B, non entrambi illustrano che l'essere di B è un requisito? Dov'è la differenza?

    
posta Mark Canlas 20.11.2013 - 19:20
fonte

2 risposte

11

Se la caratteristica A si estende a B, quindi la miscelazione in A ti dà precisamente B più qualsiasi cosa A aggiunge o estende. Al contrario, se il tratto A ha un riferimento personale che viene esplicitamente digitato come B, allora la classe genitore finale deve anche mescolare B o un tipo discendente di B (e mescolarlo in prima , che è importante).

Questa è la differenza più importante. Nel primo caso, il tipo preciso di B viene cristallizzato nel punto A estendendolo. Nel secondo, il progettista della classe genitore decide quale versione di B viene utilizzata, nel punto in cui è composta la classe genitore.

Un'altra differenza è dove A e B forniscono metodi con lo stesso nome. Dove A estende B, il metodo di A sovrasta B's. Dove A viene mischiato dopo B, il metodo di A vince semplicemente.

Il riferimento automatico tipizzato ti dà molta più libertà; l'accoppiamento tra A e B è allentato.

UPDATE:

Poiché non sei chiaro sul vantaggio di queste differenze ...

Se usi l'ereditarietà diretta, crei la caratteristica A che è B + A. Hai impostato la relazione in pietra.

Se utilizzi un riferimento automatico digitato, chiunque desideri utilizzare la caratteristica A nella classe C potrebbe

  • Mix B e poi A in C.
  • Mescola un sottotipo di B e poi A in C
  • Metti A in C, dove C è una sottoclasse di B.

E questo non è il limite delle loro opzioni, dato il modo in cui Scala ti permette di istanziare un tratto direttamente con un blocco di codice come suo costruttore.

Per quanto riguarda la differenza tra il metodo di A vincere , perché A è mischiato per ultimo, rispetto a A che estende B, considera questo ...

Dove mischiate una sequenza di tratti, ogni volta che viene invocato il metodo foo() , il compilatore raggiunge l'ultima caratteristica mescolata per cercare foo() , quindi (se non viene trovato), attraversa la sequenza a sinistra fino a quando non trova un tratto che implementa foo() e lo usa. A ha anche la possibilità di chiamare super.foo() , che attraversa anche la sequenza a sinistra finché trova un'implementazione, e così via.

Quindi se A ha un riferimento automatico scritto a B e il writer di A sa che B implementa foo() , A può chiamare super.foo() sapendo che se nient'altro fornisce foo() , B lo farà. Tuttavia, il creatore della classe C ha la possibilità di eliminare qualsiasi altra caratteristica in cui implementa foo() , e A lo otterrà.

Di nuovo, questo è molto più potente e meno limitante di A che estende B e chiama direttamente la versione di B di foo() .

    
risposta data 20.11.2013 - 20:41
fonte
0

Ho un codice che illustra alcune delle differenze di visibilità e implementazioni "predefinite" durante l'estensione vs l'impostazione di un self-type. Non illustra alcuna delle parti già discusse su come vengono risolte le collisioni dei nomi effettive, ma si concentra invece su ciò che è possibile e non è possibile fare.

trait A1 {
  self: B =>

  def doit {
    println(bar)
  }
}

trait A2 extends B {
  def doit {
    println(bar)
  }
}

trait B {
  def bar = "default bar"
}

trait BX extends B {
  override def bar = "bar bx"
}

trait BY extends B {
  override def bar = "bar by"
}

object Test extends App {
  // object Thing1 extends A1  // FAIL: does not conform to A1 self-type
  object Thing1 extends A1 with B
  object Thing2 extends A2

  object Thing1X extends A1 with BX
  object Thing1Y extends A1 with BY
  object Thing2X extends A2 with BX
  object Thing2Y extends A2 with BY

  Thing1.doit  // default bar
  Thing2.doit  // default bar
  Thing1X.doit // bar bx
  Thing1Y.doit // bar by
  Thing2X.doit // bar bx
  Thing2Y.doit // bar by

  // up-cast
  val a1: A1 = Thing1Y
  val a2: A2 = Thing2Y

  // println(a1.bar)    // FAIL: not visible
  println(a2.bar)       // bar bx
  // println(a2.bary)   // FAIL: not visible
  println(Thing2Y.bary) // 42
}

Una importante differenza IMO è che A1 non espone che è necessario B a qualsiasi cosa che la vede semplicemente come A1 (come illustrato nelle parti up-cast). L'unico codice che vedrà effettivamente una particolare specializzazione di B è il codice che conosce esplicitamente il tipo composto (come Think*{X,Y} ).

Un altro punto è che A2 (con estensione) userà effettivamente B se non viene specificato nient'altro mentre A1 (autotipo) non dice che userà B a meno che non sia sovrascritto, un B concreto deve essere esplicitamente fornito quando gli oggetti vengono istanziati.

    
risposta data 10.04.2014 - 13:54
fonte

Leggi altre domande sui tag