2015-02-23 13 views
5

Voglio eliminare un cast di esecuzione su un generico (asInstanceOf[A]) senza conversioni implicite.Copia di restituzione della classe del caso dalla funzione generica senza cast di esecuzione

Ciò accade quando ho un modello di dati abbastanza pulito costituito da case classes con un tratto comune e voglio implementare un algoritmo generico su di esso. Ad esempio, l'algoritmo risultante dovrebbe prendere una classe di tipo A che è una sottoclasse di trait T e deve restituire una copia della classe di calcestruzzo A con un campo aggiornato.

Questo è facile da ottenere quando posso semplicemente aggiungere un metodo astratto copy al tratto base e implementarlo in tutte le sottoclassi. Tuttavia questo potenzialmente inquina il modello con metodi richiesti solo da determinati algoritmi e talvolta non è possibile perché il modello potrebbe essere fuori dal mio controllo.

Ecco un esempio semplificato per dimostrare il problema e una soluzione che utilizza i cast di esecuzione.

Per favore non rimanere impigliato nei dettagli.

Supponiamo che ci sia un tratto e alcune classi di casi che non posso cambiare:

trait Share { 
    def absolute: Int 
} 

case class CommonShare(
    issuedOn: String, 
    absolute: Int, 
    percentOfCompany: Float) 
    extends Share 

case class PreferredShare(
    issuedOn: String, 
    absolute: Int, 
    percentOfCompany: Float) 
    extends Share 

Ed ecco un metodo semplice per ricalcolare la corrente percentOfCompany quando il numero totale di azioni sono cambiati e aggiornare il campo in la classe caso

def recalculateShare[A <: Share](share: A, currentTotalShares: Int): A = { 

    def copyOfShareWith(newPercentage: Float) = { 
    share match { 
     case common: CommonShare => common.copy(percentOfCompany = newPercentage) 
     case preferred: PreferredShare => preferred.copy(percentOfCompany = newPercentage) 
    } 
    } 

    copyOfShareWith(share.absolute/currentTotalShares.toFloat).asInstanceOf[A] 
} 

Alcuni esempi di invocazioni sulla REPL:

scala> recalculateShare(CommonShare("2014-01-01", 100, 0.5f), 400) 
res0: CommonShare = CommonShare(2014-01-01,100,0.25) 

scala> recalculateShare(PreferredShare("2014-01-01", 50, 0.5f), 400) 
res1: PreferredShare = PreferredShare(2014-01-01,50,0.125) 

Quindi funziona e per quanto ho capito la chiamata .asInstanceOf[A] non fallirà mai ma è richiesta per compilare il codice. C'è un modo per evitare il cast runtime in modo sicuro al tipo senza conversioni implicite?

+0

Io non la penso così. Avresti bisogno di una prova implicita del typetag. In caso contrario, si ottiene un problema di cancellazione del tipo. –

risposta

4

Hai un paio di opzioni a cui riesco a pensare, e in genere si riduce a un equilibrio tra la portata generale di una soluzione e la quantità di verbosità che puoi tollerare.

asInstanceOf

La soluzione si sente sporca, ma non credo che sia così male, e il gnarliness è abbastanza ben contenuta.

Typeclass

Un ottimo approccio per fornire il comportamento di tipi di dati, pur mantenendo la separazione degli interessi nel codice è l'arricchire il vostro modello Library/typeclass. Vorrei avere un riferimento perfetto per questo, ma non lo faccio. Cerca quei termini o "classe implicita" e dovresti essere in grado di trovare esempi sufficienti per ottenere la deriva.

È possibile creare una classe di tipi trait Copyable[A] { def copy(?): A } (implicit class) e creare delle istanze per ciascun tipo. Il problema qui è che è un po 'prolisso, specialmente se si desidera che il metodo copy sia completamente generico. Ho lasciato il suo elenco di parametri come un punto interrogativo perché potresti semplicemente adattarlo strettamente a ciò che effettivamente ti serve, o potresti provare a farlo funzionare per qualsiasi , il che sarebbe abbastanza difficile, per quanto ne so.

Ottica

Le lenti sono state fatte per risolvere questo tipo di imbarazzo. Si consiglia di controllare Monocle, che è un buon approccio generico a questo problema. Anche se ancora non risolve il problema della verbosità, potrebbe essere il modo di procedere se questo problema si ripresenta durante il tuo progetto, e specialmente se ti trovi a cercare di apportare modifiche in profondità all'interno del tuo oggetto grafico.

+0

Risposta molto ben scritta, grazie! Devo provare Monocle, ma presumo che avrei bisogno di passare l'obiettivo nella mia funzione generica insieme agli altri parametri, il che rende il codice client della mia funzione generica più complicato. Per quanto riguarda le classiche, ho provato questo approccio, ma non l'ho fatto funzionare. Inoltre, i miei tentativi mi hanno sempre avvertito che dovevo abilitare le conversioni implicite, che è esattamente quello che volevo evitare. Ho sbagliato? –

+0

Non sono sicuro, non ho effettivamente provato Monocle. Può generare automaticamente obiettivi, ma sembra che sarebbe l'ideale se questi fossero collegati in modo implicito in qualche modo. E nel tuo caso, sembra che sarebbe l'ideale se ci fosse un modo semplice per definire un obiettivo sull'interfaccia condivisa. IMO, Scala fa male un po 'di non avere un supporto diretto migliore per questo. – acjay

Problemi correlati