2012-08-11 11 views
8

Un metodo di classi di casi copy() deve creare una copia identica dell'istanza, oltre a sostituire qualsiasi campo per nome. Questo sembra fallire quando la classe case ha parametri di tipo con manifest. La copia perde tutta la conoscenza dei tipi dei suoi parametri.Scala: Come rendere la copia della classe del caso mantenere le informazioni manifest

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" 

Perché Foo.copy perde il manifesto sui parametri e come faccio a farlo conservarlo?

risposta

15

Diverse peculiarità di Scala interagiscono per fornire questo comportamento. La prima cosa è che i numeri Manifest non vengono aggiunti solo all'elenco dei parametri impliciti segreti sul costruttore ma anche sul metodo di copia. E 'ben noto che

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

è lo zucchero sintattico per

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

ma questo riguarda anche il costruttore di copia, che sarebbe simile a questa

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

Tutti quelli implicit m s sono creati da th compilatore e inviato al metodo attraverso l'elenco dei parametri impliciti.

Questo andrebbe bene finché si utilizzava il metodo copy in un punto in cui il compilatore conosceva il parametro di tipo Foo s. Ad esempio, questo funzionerà al di fuori della classe Bar:

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

Questo funziona perché il compilatore ne deduce che foo è un Foo[Int] quindi sa che foo.a è un Int in modo che possa chiamare copy come questo:

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

(si noti che manifest[T]() è una funzione che crea una rappresentazione manifesta del tipo T, ad esempio Manifest[T] con la "M". Non mostrato è l'una ddizione del parametro predefinito in copy.) Funziona anche all'interno della classe Foo perché contiene già il manifest passato durante la creazione della classe. Sarebbe simile a questa:

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" 

Nell'esempio originale, tuttavia, non riesce nella classe Bar a causa della seconda peculiarità: mentre i parametri di tipo di Bar sono conosciuti all'interno della classe Bar, i parametri di tipo di i parametri del tipo non lo sono. Sa che A in Bar è un Foo o un SubFoo o SubSubFoo, ma non se è un Foo[Int] o un Foo[String]. Questo è, ovviamente, il ben noto problema di cancellazione di tipo in Scala, ma qui sembra un problema anche quando non sembra che la classe stia facendo qualcosa con il tipo di parametro di tipo foo s. Ma lo è, ricorda che c'è un'iniezione segreta di un manifest ogni volta che viene chiamato lo copy e quelli che si manifestano sovrascrivono quelli che c'erano prima.Dal momento che la classe Bar non ha alcuna idea era il parametro tipo di foo è, esso crea solo un manifesto di Any e invia che insieme in questo modo:

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

Se uno ha il controllo sulla classe Foo (ad esempio, non è List) poi un soluzione che facendo tutto la copia sopra nella classe Foo con l'aggiunta di un metodo che farà la corretta copia, come localCopy sopra, e restituire il risultato:

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" 

Un'altra soluzione è quella di aggiungere Foo s parametro di tipo come un parametro di tipo manifestata di Bar:

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

Ma questo scale male se gerarchia delle classi è di grandi dimensioni, (vale a dire più membri hanno parametri di tipo, e quelle classi hanno anche parametri di tipo) poiché ogni classe dovrebbe avere i parametri di tipo di ogni classe sotto di essa. Sembra anche a fare il tipo di inferenza fuori di testa quando si cerca di costruire un Bar:

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

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

Buon lavoro, amico mio! –

1

Ci sono due problemi, come si identificato. Il primo problema è il problema di cancellazione del tipo all'interno di Bar, dove Bar non conosce il tipo di manifest di Foo. Personalmente utilizzerei la soluzione alternativa localCopy da te suggerita.

Il secondo problema è che un'altra implicita viene iniettata segretamente in copy. Questo problema viene risolto passando di nuovo esplicitamente il valore in copy. Per esempio:

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 

Vediamo la copia ha mantenuto il Int manifesta ma il tipo di fooCopy e res2 è Manifest[Any] a causa di cancellazione.

Perché avevo bisogno di accedere alle prove implicite per fare il copy ho dovuto usare la sintassi esplicita implicit (hah) invece della sintassi legata al contesto. Ma utilizzando la sintassi esplicita causati errori:

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? Come mai la sintassi legata al contesto funziona e l'esplicito implicit non funziona? Ho scavato e ho trovato una soluzione al problema: l'annotazione @uncheckedVariance.

UPDATE

Ho scavato intorno un po 'e ha scoperto che in Scala 2.10 classi case sono stati cambiati per solo copiare i campi dalla prima lista dei parametri in copy().

Martin dice: case class ness viene assegnato solo al primo argomento elenco il resto non deve essere copiato.

Vedere i dettagli di questa modifica allo https://issues.scala-lang.org/browse/SI-5009.

+0

Stavo cercando di farlo ma non sapevo di '@ uncheckedVariance'. Questo può portare ad alcuni compiti non corretti, o è il solo bisogno che sia un artefatto del fatto che non ci siano Manifest' separati di covarianti e controvarianti in Scala? – drhagen

+0

Penso che sia solo un artefatto. Ero frustrato dal fatto che la sintassi legata al contesto funzionasse, ma la sintassi implicita esplicita no, quindi ho cercato e trovato '@ uncheckedVariance'. La mia ipotesi è che la sintassi legata al contesto stia facendo '@ uncheckedVariance' dietro le quinte. – sourcedelica

+0

Aggiunte informazioni su '@ uncheckedVariance' alla risposta. – sourcedelica