Una possibile soluzione, che può coprire alcuni casi d'uso, è quello di utilizzare Lens
es dalla shapeless
libreria:
import shapeless._
abstract class Foox[T](
implicit l: MkFieldLens.Aux[T, Witness.`'age`.T, Int]
) {
self: T =>
final private val ageLens = lens[T] >> 'age
def age: Int
def addToAge(i: Int): T = ageLens.modify(self)(_ + i)
}
case class Dog(breed: Breed, age: Int) extends Foox[Dog]
case class Person(name: String, age: Int) extends Foox[Person]
Si noti che per creare un Lens
è necessario un implicito MkFieldLens
, così è più facile per definire Foox
come abstract class
anziché trait
. Altrimenti dovresti scrivere del codice in ogni bambino per fornire quello implicito.
Inoltre, non credo ci sia un modo per evitare di definire un age: Int
in ogni bambino. Devi fornire l'età in qualche modo quando costruisci un'istanza, ad es. Dog(Pincher, 5)
, quindi devi avere quell'argomento costruttore per età lì.
Qualche spiegazione in più:
Prendendo in prestito da un Haskell Lens tutorial:
Un obiettivo è una prima classe riferimento ad una sezione di un certo tipo di dati. [...] Dato un obiettivo ci sono essenzialmente tre cose che potrebbe desiderare di fare
- Vedere le sottoparte
- Modificare l'intero cambiando la sottoparte
- Combinate questo obiettivo con un altro obiettivo di guardare ancora più profondo
il primo e il secondo danno luogo l'idea che le lenti sono getter e setter come si potrebbe avere su un oggetto.
La parte di modifica può essere utilizzata per implementare ciò che vogliamo fare con age
.
La libreria Shapeless fornisce una sintassi graziosa e senza piastra per definire e utilizzare obiettivi per i campi della classe caso. The code example in the documentation è auto esplicativo, credo.
Il seguente codice per il campo age
Da tale esempio:
final private val ageLens = lens[???] >> 'age
def age: Int
def addToAge(i: Int): ??? = ageLens.modify(self)(_ + i)
Quale dovrebbe essere il tipo di ritorno di addToAge
essere? Dovrebbe essere il tipo esatto della sottoclasse da cui viene chiamato questo metodo. Solitamente questo viene ottenuto con F-bounded polymorphism. Quindi abbiamo la seguente:
trait Foox[T] { self: T => // variation of F-bounded polymorphism
final private val ageLens = lens[T] >> 'age
def age: Int
def addToAge(i: Int): T = ageLens.modify(self)(_ + i)
}
T
viene utilizzato lì come il tipo esatto del bambino, ed ogni classe che estende Foox[T]
dovrebbe dotarsi di T
(a causa della dichiarazione di auto-tipo self: T =>
). Ad esempio:
case class Dog(/* ... */) extends Foox[Dog]
Ora abbiamo bisogno di fare il lavoro di linea lens[T] >> 'age
.
Analizziamo la firma del metodo >>
per vedere che cosa ha bisogno per funzionare:
def >>(k: Witness)(implicit mkLens: MkFieldLens[A, k.T]): Lens[S, mkLens.Elem]
vediamo che l'argomento 'age
viene implicitamente convertito in un shapeless.Witness
. Witness
rappresenta il tipo esatto di un valore specifico o, in altre parole, un valore a livello di carattere. Due diversi valori letterali, ad es. Symbol
s 'age
e 'foo
, hanno diversi testimoni e quindi i loro tipi possono essere distinti.
Shapeless fornisce una sintassi di invenzioni stravagante per ottenere un valore Witness
. Per 'age
simbolo:
Witness.`'age` // Witness object
Witness.`'age`.T // Specific type of the 'age symbol
seguito dalla voce 1 e la firma >>
, abbiamo bisogno di avere un implicito MkFieldLens
a disposizione, per la classe T
(il bambino case class
) e il campo 'age
:
MkFieldLens[T, Witness.`'age`.T]
Il Il campo age
dovrebbe anche avere il tipo Int
. E 'possibile esprimere questa esigenza con l'Aux
pattern comune in informe:
MkFieldLens.Aux[T, Witness.`'age`.T, Int]
E per fornire questa implicita in modo più naturale, come un argomento implicito, dobbiamo usare un abstract class
invece di un trait
.
Ok, sono davvero curioso di questo. Questa domanda sembra in qualche modo simile a (ma non un duplicato di) [questo] (http://stackoverflow.com/questions/7227641/scala-how-can-i-make-my-immutable-classes-easier-to-subclass) e [this] (http://stackoverflow.com/questions/21560470/method-inheritance-in-immutable-classes). Qualcuno sa se le risposte fornite lì aiutano affatto con questa domanda? –
È possibile che si stia violando il principio dell'immutabilità se si tenta di modificare lo stato dell'oggetto stesso. Pertanto 'copia' sembra un approccio. Lo stato dovrebbe invece essere gestito dal codice che utilizza questi oggetti, ad esempio aggiorna il proprio stato per utilizzare l'oggetto restituito da 'addToAge' – tuxdna
Non credo ci sia una risposta facile qui. Il pensiero più vicino che mi viene in mente è usare una qualche forma di lente o usare la libreria informe.Devi abbandonare OO e lavorare con astrazioni più funzionali. Ad esempio, se tu avessi un 'map()' che semplicemente trasforma l'età e lascia tutto il resto come si otterrebbe in questo caso ciò di cui hai bisogno. – marios