2014-04-21 28 views
6

Załóżmy, że mamy wspólną cechę Model.Czy jest możliwe zaktualizowanie pól dowolnej klasy przypadku implementującej wspólną cechę?

trait Model { 
    def id: String 
    def updated: Date 
} 

Mamy 2 klasy przypadków rozszerzające tę cechę.

case class C1(id: String, updated: Date, foo: String) extends Model 
case class C2(id: String, updated: Date, bar: Int) extends Model 

Czy można napisać funkcję narzędzia podobną do poniższej, która pobiera parametr Model jako i zwraca kopię o zaktualizowanej wartości zaktualizowanego pola?

object Model { 
    def update[T <: Model](model: T): T = { 
     model.copy(updated = new Date) // This code does not compile. 
    } 
} 
+1

[Nie] (http://stackoverflow.com/q/12370244/298389) –

+2

@ Om-nom-nom tak ;-) –

+1

@MilesSabin [Widziałem tę abstrakcję nad arity został wymieniony] (http://stackoverflow.com/a/12492635/298389), ale takie nadmierne przesuwanie jest mało realną opcją (teraz możesz swobodnie nazywać mnie "stronniczym" lub nawet kretynem :-)). Czy jest jeszcze jakiś inny sposób? –

Odpowiedz

1

copy to metoda zdefiniowana w klasach spraw. Nie na podstawie twojej podstawowej cechy Model. Co zrobić, jeśli trzeba, że:

trait Model { 
    def id: String 
    def updated: Date 
} 

case class C1(id: String, updated: Date, foo: String) extends Model 
case class C2(id: String, updated: Date, bar: Int) extends Model 
class NotACaseClass(val id: String, val updated: Date) extends Model 

NotACaseClass jest bardzo ważna dzieckiem Model, i można przejść instancję nim do funkcji update, ale powodzenia w znalezieniu sposobu copy :)

+2

Odpowiada * dlaczego to nie działa *, ale nie zapewnia rozwiązania danego problemu. –

3

Twój kod ma dwie sprawy:

  1. copy nie jest zdefiniowany na cechę, więc musisz mieć coś zdefiniowanego na cechę, której możesz użyć.
  2. Aby update zwrócił T zamiast Model, każdy Model musi znać swój rzeczywisty podtyp.

Można go naprawić tak:

trait Model[T <: Model[T]] { 
    def id: String 
    def updated: Date 
    def withDate(d: Date): T 
} 

case class C1(id: String, updated: Date, foo: String) extends Model[C1] { def withDate(d: Date) = copy(updated = d) } 
case class C2(id: String, updated: Date, bar: Int) extends Model[C2] { def withDate(d: Date) = copy(updated = d) } 

object Model { 
    def update[T <: Model[T]](model: T): T = { 
    model.withDate(new Date) // This code does not compile. 
    } 
} 

Więc teraz to działa:

scala> val c1 = C1("test", new Date, "foo") 
c1: C1 = C1(test,Mon Apr 21 10:25:10 CDT 2014,foo) 

scala> Model.update(c1) 
res0: C1 = C1(test,Mon Apr 21 10:25:17 CDT 2014,foo) 
+0

Tak właśnie planowałem. Ale wciąż zastanawiam się, czy istnieje sposób, aby zrobić to automagicznie i bezpiecznie. – fcs

+0

Więc nawet jeśli 'withDate' jest tą samą linią kodu dla każdego podtypu' Modelu ', to musi zostać przepisane dla każdej klasy, prawda? –

3

"Najlepsze" abstrakcji można pisać tutaj byłoby Lens który wygląda następująco:

trait Lens[A, B]{ 
    def get: A => B 
    def set: (A, B) => A 
} 

tak, aby Twój kod wyglądał następująco:

def update[A](that: A, value: Date)(implicit tLens: Lens[A, Date]): A = 
    tLens set (that, value) 
+0

Obiektyw jest dobry ... ale myślę, że prawdopodobnie możemy uzyskać obiektyw podany bezkształtnym 'LabelledGeneric' dla omawianych klas przypadków. –

+1

@MilesSabin Tak, założę się, że masz rację. Jeśli to napiszesz, podniosę tę odpowiedź. – wheaties