2013-03-15 22 views
9

Ho due classi PixelObject, ImageRefObject e altre ancora, ma qui ci sono solo queste due classi per semplificare le cose. Sono tutte sottoclassi di un trait Object che contiene un UID. Ho bisogno di un metodo universale che copierà un'istanza della classe case con un nuovo dato uid. Il motivo per cui ne ho bisogno perché il mio compito è quello di creare un ObjectRepository di classe che salverà l'istanza di qualsiasi sottoclasse di Object e restituirà il nuovo uid. Il mio tentativo:Scala case class copia con tipo generico

trait Object { 
    val uid: Option[String] 
} 

trait UidBuilder[A <: Object] { 
    def withUid(uid: String): A = { 
    this match { 
     case x: PixelObject => x.copy(uid = Some(uid)) 
     case x: ImageRefObject => x.copy(uid = Some(uid)) 
    } 
    } 
} 

case class PixelObject(uid: Option[String], targetUrl: String) extends Object with UidBuilder[PixelObject] 

case class ImageRefObject(uid: Option[String], targetUrl: String, imageUrl: String) extends Object with UidBuilder[ImageRefObject] 

val pix = PixelObject(Some("oldUid"), "http://example.com") 

val newPix = pix.withUid("newUid") 

println(newPix.toString) 

ma sto ottenendo il seguente errore:

➜ ~ scala /tmp/1.scala 
/tmp/1.scala:9: error: type mismatch; 
found : this.PixelObject 
required: A 
     case x: PixelObject => x.copy(uid = Some(uid)) 
           ^
/tmp/1.scala:10: error: type mismatch; 
found : this.ImageRefObject 
required: A 
     case x: ImageRefObject => x.copy(uid = Some(uid)) 
            ^
two errors found 

risposta

1

Sicuramente una soluzione migliore sarebbe quella di utilizzare in realtà il sottotipo?

trait Object { 
    val uid: Option[String] 
    def withNewUID(newUid: String): Object 
} 
0

Il lancio su A fa il trucco, probabilmente a causa della definizione ricorsiva delle classi del caso.

trait UidBuilder[A <: Object] { 
    def withUid(uid: String): A = { 
    this match { 
     case x: PixelObject => x.copy(uid = Some(uid)).asInstanceOf[A] 
     case x: ImageRefObject => x.copy(uid = Some(uid)).asInstanceOf[A] 
    } 
    } 
} 

Forse c'è un soluzione più elegante (ad eccezione - ben attuazione del withUid per ogni classe caso, che credo sia non quello che hai chiesto), ma questo funziona. :) Penso che forse non è un'idea semplice farlo con l'UidBuilder, ma è comunque un approccio interessante.

Per essere sicuri di non dimenticare un caso - e lo prendo tutte le classi di casi necessari si trovano nella stessa unità di compilazione in ogni caso - rendere il vostro Object un sealed abstract class e aggiungere un altro fuso

this.asInstanceOf[Object] 

Se lascia un caso per una delle tue classi di casi, quindi riceverai un avviso.

8

Vorrei attenermi alla soluzione proposta da Seam. Ho fatto lo stesso un paio di mesi fa. Per esempio:

trait Entity[E <: Entity[E]] { 
    // self-typing to E to force withId to return this type 
    self: E => def id: Option[Long] 
    def withId(id: Long): E 
} 
case class Foo extends Entity[Foo] { 
    def withId(id:Long) = this.copy(id = Some(id)) 
} 

Così, invece di definire un UuiBuilder con una corrispondenza per tutte le implementazioni del vostro carattere, si definisce il metodo nell'implementazione stessa. Probabilmente non vuoi modificare UuiBuilder ogni volta che aggiungi una nuova implementazione.

Inoltre, vorrei anche raccomandare di usare un auto typing per forzare il tipo di ritorno del tuo metodo withId().

Problemi correlati