2012-08-11 12 views
8

Metoda klas przypadków copy() ma na celu utworzenie identycznej kopii instancji plus zastąpienie pól nazwami. Wydaje się to nie udać, gdy klasa sprawy ma parametry typu z manifestami. Kopia traci całą wiedzę na temat typów jej parametrów.Scala: Jak wykonać kopię klasy egzemplarza zachować informacje manifestu

case class Foo[+A : Manifest](a: A) { 
    // Capture manifest so we can observe it 
    // A demonstration with collect would work equally well 
    def myManifest = implicitly[Manifest[_ <: A]] 
} 

case class Bar[A <: Foo[Any]](foo: A) { 
    // A simple copy of foo 
    def fooCopy = foo.copy() 
} 

val foo = Foo(1) 
val bar = Bar(foo) 

println(bar.foo.myManifest)  // Prints "Int" 
println(bar.fooCopy.myManifest) // Prints "Any" 

Dlaczego Foo.copy stracić manifest na parametrach i jak mogę zrobić to zachować ją?

Odpowiedz

15

Kilka osobliwości Scala wchodzi w interakcję, aby dać to zachowanie. Po pierwsze, są one dołączane nie tylko do tajnej listy niejawnych parametrów konstruktora, ale także do metody kopiowania. Powszechnie wiadomo, że

case class Foo[+A : Manifest](a: A)

jest cukier tylko syntaktyczny dla

case class Foo[+A](a: A)(implicit m: Manifest[A])

ale również wpływa konstruktor kopiujący, który wyglądałby następująco

def copy[B](a: B = a)(implicit m: Manifest[B]) = Foo[B](a)(m)

Wszystkie te implicit m s są tworzone przez th e kompilator i wysłany do metody poprzez niejawną listę parametrów.

To byłoby w porządku tak długo, jak długo używano metody copy w miejscu, w którym kompilator znał parametr typu: . Na przykład, to będzie działać na zewnątrz klasy Bar:

val foo = Foo(1) 
val aCopy = foo.copy() 
println(aCopy.myManifest) // Prints "Int" 

To działa, ponieważ kompilator wnioskuje że foo jest Foo[Int] więc wie, że foo.a jest Int więc można go nazwać copy tak:

val aCopy = foo.copy()(manifest[Int]())

(Zauważ, że manifest[T]() to funkcja, która tworzy oczywisty reprezentacji typu T, np Manifest[T] przez duże „M”. Nie pokazano Czy a ddycja domyślnego parametru do copy.) Działa również w klasie Foo, ponieważ ma już manifest, który został przekazany podczas tworzenia klasy. To mniej więcej tak wyglądać:

case class Foo[+A : Manifest](a: A) { 
    def myManifest = implicitly[Manifest[_ <: A]] 

    def localCopy = copy() 
} 

val foo = Foo(1) 
println(foo.localCopy.myManifest) // Prints "Int" 

W oryginalnym przykład, jednak nie w klasie Bar bo drugiego osobliwość: podczas gdy parametry Rodzaj Bar są znane w klasie Bar, parametry Rodzaj parametry typu nie są. Wie, że A w Bar jest lub SubSubFoo, ale nie, jeśli jest to Foo[Int] lub. Jest to oczywiście dobrze znany problem usuwania czcionek w Scali, ale pojawia się tutaj problem, nawet jeśli nie wydaje się, aby klasa wykonywała cokolwiek z typem parametru typu: foo. Ale tak jest, pamiętaj, że istnieje tajny zastrzyk manifestu za każdym razem, gdy jest wywoływany, a te manifesty zastępują te, które były tam wcześniej.Ponieważ klasa Bar nie ma pomysł był parametr typ foo jest, po prostu tworzy manifest Any i wysyła że wzdłuż tak:

def fooCopy = foo.copy()(manifest[Any])

Jeśli ktoś ma kontrolę nad klasą Foo (np nie jest List) następnie jeden obejście go wykonując wszystkie kopiowanie w klasie Foo dodając metodę, która zrobi właściwą kopiowanie, jak localCopy powyżej, i zwraca wynik:

case class Bar[A <: Foo[Any]](foo: A) { 
    //def fooCopy = foo.copy() 
    def fooCopy = foo.localCopy 
} 

val bar = Bar(Foo(1)) 
println(bar.fooCopy.myManifest) // Prints "Int" 

Innym rozwiązaniem jest dodanie Foo s typ parametru jako przejawionego parametru typu Bar:

case class Bar[A <: Foo[B], B : Manifest](foo: A) { 
    def fooCopy = foo.copy() 
} 

Ale to źle, jeśli klasa Wagi hierarchia jest duża (to znaczy więcej członków ma parametry typu, a te klasy również mają parametry typu), ponieważ każda klasa musiałaby mieć parametry typu każdej klasy poniżej. Wydaje się również, aby rodzaj wnioskowania freak out gdy próbuje skonstruować Bar:

val bar = Bar(Foo(1)) // Does not compile 

val bar = Bar[Foo[Int], Int](Foo(1)) // Compiles 
+0

Dobra robota, przyjacielu! –

1

Istnieją dwa problemy, jak zidentyfikowane. Pierwszym problemem jest problem z wymazywaniem typów wewnątrz Bar, gdzie Bar nie zna typu manifestu Foo. Osobiście skorzystam z obejścia, które zasugerowałeś.

Drugą kwestią jest to, że inny domniemany jest potajemnie wstrzykiwany do copy. Ten problem został rozwiązany przez jawne przekazanie wartości do copy. Na przykład:

scala> case class Foo[+A](a: A)(implicit val m: Manifest[A @uncheckedVariance]) 
defined class Foo 

scala> case class Bar[A <: Foo[Any]](foo: A) { 
    | def fooCopy = foo.copy()(foo.m) 
    | } 
defined class Bar 

scala> val foo = Foo(1) 
foo: Foo[Int] = Foo(1) 

scala> val bar = Bar(foo) 
bar: Bar[Foo[Int]] = Bar(Foo(1)) 

scala> bar.fooCopy.m 
res2: Manifest[Any] = Int 

Widzimy kopia zachowała manifest Int ale rodzaj fooCopy i res2 jest Manifest[Any] spowodowane usunięciem.

Ponieważ potrzebowałem dostępu do ukrytych dowodów, aby wykonać copy, musiałem użyć jawnej składni implicit (hah) zamiast składni związanej z kontekstem. Ale użycie jawnej składni spowodowało błędy:

scala> case class Foo[+A](a: A)(implicit val m: Manifest[A]) 
<console>:7: error: covariant type A occurs in invariant position in type => Manifest[A] of value m 
     case class Foo[+A](a: A)(implicit val m: Manifest[A]) 
             ^
scala> case class Foo[+A](a: A)(implicit val m: Manifest[_ <: A]) 
defined class Foo 

scala> val foo = Foo(1) 
<console>:9: error: No Manifest available for Int. 

WTF? W jaki sposób działa składnia kontekstu i wyraźna implicit nie? Wykopałem i znalazłem rozwiązanie problemu: adnotację @uncheckedVariance.

UPDATE

I wykopali wokół trochę więcej i stwierdził, że w przypadku zajęcia Scala 2.10 zostały zmienione na tylko skopiować pola z pierwszej listy parametrów w copy().

Martin mówi: case class ness jest nadawany tylko na pierwszym argumencie lista pozostałych nie powinna być kopiowana.

Zobacz szczegóły tej zmiany pod numerem https://issues.scala-lang.org/browse/SI-5009.

+0

Próbowałem to zrobić, ale nie wiedział o "@ niezaznaczoneVariance". Czy może to prowadzić do pewnych niesłusznych zadań, czy też jest to potrzebne tylko jako artefakt dotyczący faktu, że nie ma oddzielnego kowariantnego i sprzecznego ze sobą "Manifestu" w Scali? – drhagen

+0

Myślę, że to tylko artefakt. Byłem sfrustrowany, że składnia związana z kontekstem działała, ale wyraźna niejawna składnia nie działała, więc wykopałem się i znalazłem '@ uncheckedVariance'. Domyślam się, że składnia związana z kontekstem w zasadzie wykonuje "@ uncheckedVariance" za kulisami. – sourcedelica

+0

Dodano informacje na temat "@ uncheckedVariance" do odpowiedzi. – sourcedelica

Powiązane problemy