2010-10-18 9 views
9

io non sono sicuro se c'è un modo migliore di fare questo:Problema "MyType": Devo usare tipi astratti (o generici) in Scala per restituire la classe effettiva?

trait Animal { 
    val name: String 
    val weight: Int 

    type SubAnimal <: Animal 

    def updateName(n: String) = returnMe(n, this.weight) 
    def updateWeight(w: Int) = returnMe(this.name, w) 
    // Abstract protected method 
    protected def returnMe(n: String, w: Int): SubAnimal 
} 

case class Dog(name: String, weight: Int) extends Animal { 
    type SubAnimal = Dog 
    override def returnMe(n: String, w: Int): Dog = Dog("Dog: " + name, w) 
} 
case class Cat(name: String, weight: Int) extends Animal { 
    type SubAnimal = Cat 
    override def returnMe(n: String, w: Int): Cat = Cat("Cat: " + name, w) 
} 

val fido = Dog("Fido", 11) 
println(fido) 
val fido2 = fido.updateWeight(12) 
println(fido2) 

Quando eseguo il codice ottengo questo output:

$ scala animal.scala 
Dog(Fido,11) 
Dog(Dog: Fido,12) 

voglio restituire il tipo effettivo dell'animale in questione dopo che è stato chiamato il updateName o updateWeight (ovvero non Animal). So che se si esegue l'override direttamente su updateName e updateWeight, verrà restituito il tipo corretto e non è necessario utilizzare il tipo astratto SubAnimal.

Esiste qualche modo complicato di sfuggire al tipo astratto per il caso speciale in cui il valore del tipo astratto è lo uguale alla sottoclasse?

(questo è noto come problema "MyType").

risposta

6

Alcuni recent discussion su questo argomento ... quello che stai cercando è comunemente indicato come "MyType", e il tipico codifica Scala/Java per esso utilizza i parametri di tipo ricorsive:

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable { 
    public final int compareTo(E o) 
    // ... 
} 
+0

Quindi il modo magico sarebbe usare "this.type" (dal tuo link) ma dal momento che sto restituendo una nuova istanza (non questa), allora non è possibile. Più avanti nel post Odersky raccomanda i tipi astratti per questo problema, quindi immagino che termini la discussione. Grazie. –

+0

Sì, questo tipo è troppo stretto per agire come MyType: è il tipo di questo * ma nessun'altra istanza di questa classe *. –

6

Utilizzo della parametrizzazione del tipo?

trait Animal[A <: Animal[A]] { 
    val name: String 
    val weight: Int 

    def updateName(n: String) = returnMe(n, this.weight) 
    def updateWeight(w: Int) = returnMe(this.name, w) 
    // Abstract protected method 
    protected def returnMe(n: String, w: Int): A 
} 

case class Dog(name: String, weight: Int) extends Animal[Dog] { 
    override def returnMe(n: String, w: Int) = Dog("Dog: " + name, w) 
} 

case class Cat(name: String, weight: Int) extends Animal[Cat] { 
    override def returnMe(n: String, w: Int) = Cat("Cat: " + name, w) 
} 
+0

Hmm .. la vostra soluzione è probabilmente ciò che "tipi astratti" aveva lo scopo di semplificare. Considero la "parametrizzazione del tipo" altrettanto cattiva/buona di "tipi astratti" in questo caso. Sei d'accordo? –

+0

@olle: Non ho mai capito il punto dei membri di tipo astratto e non li uso mai nel mio codice. Li considero una ridondanza inutile nella lingua. (Paul P ha recentemente espresso pensieri simili alla mailing list.) –

+0

Sei corretto, i tipi astratti sembrano essere puro zucchero. Ma a me piacciono molto, dal momento che penso sia più facile pensare a un "campo tipo" che puoi sovrascrivere nella sottoclasse, rispetto alla sintassi del parametro di testo corrispondente. –

9

Questo dovrebbe funzionare:

trait Animal[T] { 
    self:T => 

    val name: String 
    val weight: Int 

    def updateName(n: String): T = returnMe(n, this.weight) 
    def updateWeight(w: Int): T = returnMe(this.name, w) 
    // Abstract protected method 
    protected def returnMe(n: String, w: Int): T 
} 

case class Dog(name: String, weight: Int) extends Animal[Dog] { 
    override def returnMe(n: String, w: Int): Dog = Dog("Dog: " + name, w) 
} 

case class Cat(name: String, weight: Int) extends Animal[Cat] { 
    override def returnMe(n: String, w: Int): Cat = Cat("Cat: " + name, w) 
} 

Qualcosa di simile case class Cat(name: String, weight: Int) extends Animal[Dog] viene rifiutato dal compilatore. Codice rubato adattato da http://oldfashionedsoftware.com/2009/12/10/self-help/

+0

Questo ottiene il mio voto. Grande trucco! –

+0

Ciao @Landei, non vedo la differenza tra questo e la soluzione fornita da @ uno-zero-zero-uno. Perché il sé: T => Parte necessaria? Quando lo prendo, ottengo lo stesso risultato. –

+1

@olle: il comportamento è lo stesso della soluzione one-zero-zero-one, è solo più facile da leggere. Senza il sé: parte T, puoi scrivere 'case class Cat (nome: String, weight: Int) estende Animal [Int]', che chiaramente non dovrebbe essere consentito. Per dettagli vedi il link che ho fornito. – Landei