2012-03-02 12 views
9

Mi piacerebbe avere un tratto che può a) essere mescolato in qualsiasi classe con un metodo particolare, e b) può chiamare super. Qualcosa di simile:Tratti Scala e tipi strutturali: può un tratto estendere un tipo strutturale e quindi chiamare super?

// A and B are from a library that I don't control. No changes allowed here. 
    class A { 
    def stuff = "a stuff" 
    } 
    class B { 
    def stuff = "b stuff" 
    } 

    // My code starts here 

    type HasStuffMethod = { 
    def stuff: String 
    } 

    // Note that this doesn't compile - gets: 
    // class type required but AnyRef{def stuff: String} found 
    trait ImplementsStuff extends HasStuffMethod { 
    override def stuff = "trait + " + super.stuff 
    } 

    val a = new A with ImplementsStuff 
    assert(a.stuff == "trait + a stuff") 

    val b = new B with ImplementsStuff 
    assert(b.stuff == "trait + b stuff") 

C'è un modo per fare questo?

Si noti che non controllo A e B; vengono da un'altra libreria che non posso modificare.

[Edit - ha aggiunto dopo aver visto le risposte]

C'è un modo per chiamare il metodo originale in qualcosa di simile?

trait ImplementsStuff { 
    this: HasStuffMethod => 
    abstract override def stuff = "foo" + "how do I call the original method here?" 
    } 

Questo non è utile, dal momento che quando si mescolano in qualcosa dà:

error: overriding method stuff in class A of type => java.lang.String; method stuff in trait ImplementsStuff of type => java.lang.String cannot override a concrete member without a third member that's overridden by both (this rule is designed to prevent ``accidental overrides'')

Ma non è un caso; si, voglio davvero che tu faccia un passo su quel metodo esistente. E poi lascia che lo chiami anche io.

+2

Qui 'HasStuffMethod' non è una classe di tipo. È un alias di tipo per un tipo strutturale. – paradigmatic

+1

A destra: un modello di tipografia è un'altra cosa. Ed è irrilevante a questa domanda se 'HasStuffMethod' è un tipo strutturale o nominale (cioè un tratto regolare). – axel22

+1

Ho aggiunto una proposta di modifica alla tua domanda.Spero che chiarisca cosa stai cercando di fare. Se ho interpretato male puoi cancellarlo. Se è corretto, puoi usarlo per riformulare la tua domanda. In tal caso, modifica anche il riferimento a "typeclass" nel titolo della domanda. –

risposta

4

In questo caso è necessario un abstract override.

+0

Come si definisce il tratto? (Aggiunta una nota che la riga "tratto ImplementsStuff estende HasStuffMethod" non viene compilata.) –

+1

@JamesMoore Solo "override astratto def ...". Ma ho appena notato che è stato esaltato un 'type' -' type' è solo un alias, e il tuo alias è di tipo strutturale: non funzionerà. Rendilo estendere invece una 'classe astratta 'o un' tratto'. –

5

Mi vengono in mente due opzioni:

l'opzione 1, utilizzando un tipo di auto annotazione e chiamando stuff da un nuovo metodo (che ho chiamato fantasiosamente callStuff) invece di rilevante esso.

trait ImplementsStuff { 
    this: HasStuffMethod => 
    def callStuff = "trait + " + this.stuff 
    } 

    val a = new A with ImplementsStuff 
    assert(a.callStuff == "trait + a stuff") 

    val b = new B with ImplementsStuff 
    assert(b.callStuff == "trait + b stuff") 

opzione 2 (dal momento che si dice di non controllare A e B) è il caro vecchio modello decoratore.

trait HasStuff { def stuff: String } 

    class DecorateStuff(decorated: HasStuffMethod) extends HasStuff { 
    def stuff = "trait + " + decorated.stuff 
    } 
    val decA = new DecorateStuff(new A) 
    assert(decA.stuff == "trait + a stuff") 

    val decB = new DecorateStuff(new B) 
    assert(decB.stuff == "trait + b stuff") 
+0

Entrambi interessanti, ma nessuno dei due risolve il problema. Il primo fallisce perché non c'è modo di dire a tutti i chiamanti che devono chiamare un nuovo metodo (e quindi asserisce sicuramente di fallire). Il secondo restituisce un tipo diverso, quindi è una tecnica utile ma in realtà non aiuta qui - cosa fai quando devi passare la nuova cosa ad un altro codice che si aspetta un A, non un DecorateStuff? –

+0

@JamesMoore Hai ragione, entrambi i metodi presuppongono che tu o il tuo team controlliate il codice del chiamante. Se questo non è il caso e le tue aspettative sono di cambiare il comportamento del loro metodo roba ma hanno ancora A e B, questo sembra Aspect Oriented Programming. Puoi guardare AspectJ. [Questo articolo di Jonas Boner potrebbe aiutarti] (http://architects.dzone.com/articles/real-world-scala-managing-cros) –

+0

È solo che il sistema esistente sembra _soooo_ vicino a quello che mi piacerebbe. Nel tuo primo esempio, se potessi cambiare callStuff in cose semplici, e poi avere un modo per fare riferimento al metodo che viene sovrascritto, lo avresti. (Aggiunto questo alla mia domanda) –

1

Non v'è alcun modo per estendere un tipo raffinato a Scala (io non sono sicuro che questo potrebbe essere tirato fuori in modo sicuro, ma sarebbe interessante esplorare).

Ho una soluzione che è più dettagliata, ma ti avvicina al tuo obiettivo.

trait HasStuff { 
    def theirStuff: String 
} 

trait ImplementsStuff extends HasStuff { 
    def stuff = "trait + " + theirStuff 
} 

Si noti che invece di un tipo raffinato ho un tratto e lo estendo nell'implementazione. Devo aggiungere manualmente i superaccessori, lo faccio per delega. HasStuff li definisce e li implementano sul sito mixin:

val a = new A with ImplementsStuff { 
    def theirStuff = super[A].stuff 

    override def stuff = super[ImplementsStuff].stuff 
} 

val b = new B with ImplementsStuff { 
    def theirStuff = super[B].stuff 

    override def stuff = super[ImplementsStuff].stuff 
} 

Presso il sito mixin ho bisogno di implementare la funzione di accesso super (theirStuff) per trasmettere al metodo giusto ereditato. Devo anche eseguire l'override di stuff a questo livello, dal momento che ImplementsStuff non eredita un metodo stuff, pertanto non può essere impostato su override.

Calling stuff su a e b mostra è stato ignorato:

$ a.stuff 
res0: java.lang.String = trait + a stuff 

$ b.stuff 
res1: java.lang.String = trait + b stuff 
Problemi correlati