2016-06-11 17 views
5

Mam kilka klas wszystkich rozszerza tę samą cechę i wszystkie mają wspólną funkcję, która powinna zmienić ich stan. Jednak zastanawiałem się, czy istnieje lepszy sposób na wdrożenie tej samej funkcjonalności.scala - idiomatyczny sposób na zmianę stanu klasy

np

trait Breed 
case object Pincher extends Breed 
case object Haski extends Breed 

trait Foox{ 
    def age: Int 
    def addToAge(i: Int): Foox 
} 

case class Dog(breed: Breed, age: Int) extends Foox 
case class Person(name: String, age: Int) extends Foox 

Chcę że addToAge zwróci ten sam obiekt z dodatkową int, oczywiście mogę realizować takie same dla każdej klasy, co jest sprzeczne DRY zasadę:

case class Dog(breed: Breed, age: Int) extends Foox{ 
    def addToAge(i: Int) = copy(age = age + i) 
} 
case class Person(name: String, age: Int) extends Foox{ 
    def addToAge(i:Int) = copy(age = age + i) 
} 
  1. Czy jest lepszy sposób na uniknięcie tego?

  2. Czy istnieje możliwość uniknięcia przedefiniowania tego wieku: Int w każdej klasie przypadku i utrzymanie jego stanu (wiek jest już zdefiniowany w cechie)?

+0

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? –

+0

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

+0

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

Odpowiedz

5

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ć

  1. Zobacz na podczęści
  2. Modyfikacja całości poprzez zmianę podczęści
  3. 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] 
  1. 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 
    
  2. 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.

+0

To było podobne do tego, co miałem na myśli. Czy możemy dodać import również po to, aby ułatwić komuś powiedzenie tego w repl? – marios

+1

@marios OK, dodano. 'shapeless._' wystarczy, aby zadziałał. Dodatkowo potrzebna jest definicja "rasy" z pierwotnego pytania. – Kolmar

+0

@Kolmar, dziękuję, że to dokładnie to, czego szukałem, czy mógłbyś wyjaśnić trochę więcej na temat tego voodoo :)? – igx