Jednym z możliwych rozwiązań, które mogą obejmować niektóre przypadki użycia, jest użycie Lens
es od shapeless
Biblioteka:
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]
pamiętać, że aby stworzyć Lens
trzeba niejawna MkFieldLens
, więc łatwiej aby zdefiniować Foox
jako abstract class
zamiast trait
. W przeciwnym razie musiałbyś napisać jakiś kod w każdym dziecku, aby zapewnić to niejawne.
Ponadto, nie sądzę, że istnieje sposób, aby uniknąć zdefiniowania age: Int
w każdym dziecku. Musisz podać wiek w pewien sposób podczas konstruowania instancji, np. Dog(Pincher, 5)
, więc musisz mieć ten argument konstruktora ze względu na wiek.
Niektóre więcej wyjaśnień:
Pożyczanie od Haskell Lens tutorial:
Obiektyw jest nawiązaniem do pierwszej klasy podczęści pewnego typu danych. [...] Biorąc obiektyw istnieją zasadniczo trzy rzeczy można chcą zrobić
- Zobacz na podczęści
- Modyfikacja całości poprzez zmianę podczęści
- Kombajn ten obiektyw z innym obiektywem wyglądać jeszcze głębiej
Pierwszy i drugi dają początek idei, że soczewki pobierają , a setery takie jak ty mogą mieć na obiekcie.
Część modyfikacji może być używana do implementacji tego, co chcemy zrobić z age
.
Bezkształtna biblioteka zapewnia ładną, wolną od standardów składnię do definiowania i używania soczewek w polach klasy case. The code example in the documentation jest zrozumiałe, jak sądzę.
Poniższy kod w polu age
wynika z tego przykładu:
final private val ageLens = lens[???] >> 'age
def age: Int
def addToAge(i: Int): ??? = ageLens.modify(self)(_ + i)
Co typem powrotu addToAge
być? Powinien to być dokładny typ podklasy, z której wywoływana jest ta metoda. Zazwyczaj osiąga się to przy pomocy F-bounded polymorphism. Tak więc mamy następujące:
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
służy tam jako dokładny typ dziecka, a każda klasa rozszerzenie Foox[T]
powinien zapewnić sobie jak T
(ze względu na self-deklaracji typu self: T =>
). Na przykład:
case class Dog(/* ... */) extends Foox[Dog]
Teraz musimy wykonać tę linię lens[T] >> 'age
.
Przeanalizujmy podpis metody >>
, aby zobaczyć to, czego potrzebuje, aby funkcjonować:
def >>(k: Witness)(implicit mkLens: MkFieldLens[A, k.T]): Lens[S, mkLens.Elem]
Widzimy, że 'age
argumentem dostaje niejawnie konwertowane na shapeless.Witness
. Witness
reprezentuje dokładny typ określonej wartości lub innymi słowy wartość poziomu. Dwa różne literały, np. Symbol
s 'age
i 'foo
, mają różnych świadków, a tym samym można rozróżnić ich typy.
Shapeless dostarcza fantazyjną składnię wsteczną, aby uzyskać pewną wartość w postaci Witness
. Dla 'age
symbolem:
Witness.`'age` // Witness object
Witness.`'age`.T // Specific type of the 'age symbol
wynikających z pkt 1 i podpisem >>
, musimy mieć niejawna MkFieldLens
dostępne dla klasy T
(dziecka case class
) i pola 'age
:
MkFieldLens[T, Witness.`'age`.T]
Pole age
powinno również mieć typ Int
. Jest możliwe, aby wyrazić ten wymóg z Aux
pattern powszechne w bezkształtną:
MkFieldLens.Aux[T, Witness.`'age`.T, Int]
I dostarczyć ten niejawna bardziej naturalnie, jako domniemany argumentu, musimy użyć abstract class
zamiast trait
.
W porządku, jestem naprawdę ciekawy tego. To pytanie wydaje się nieco podobne do (ale nie duplikatu) [this] (http://stackoverflow.com/questions/7227641/scala-how-can-i-make-my-immutable-classes-sasier-to-subclass) i [this] (http://stackoverflow.com/questions/21560470/method-inheritance-in-immutable-classes). Czy ktoś wie, czy udzielone odpowiedzi pomagają w ogóle w tym pytaniu? –
Możesz naruszać zasadę niezmienności, jeśli próbujesz zmienić stan tego samego obiektu. Dlatego "kopia" wydaje się być podejściem. Zamiast tego stan powinien być utrzymywany przez kod, który używa tych obiektów, tj. Aktualizować swój własny stan, aby użyć obiektu zwróconego przez 'addToAge' – tuxdna
Nie sądzę, że istnieje łatwa odpowiedź tutaj. Najbliżej myślę, że przychodzi mi do głowy użycie soczewek lub użycie bezkształtnej biblioteki.Musisz zrezygnować z OO i pracować z bardziej funkcjonalnymi abstrakcjami. Na przykład, jeśli masz 'map()', która po prostu przekształca wiek i pozostawia wszystko inne, tak jak byś osiągnął to, czego potrzebujesz w tym przypadku. – marios