2012-08-11 58 views
8

案例類copy()方法應該創建實例的相同副本,並根據名稱替換任何字段。當case類具有帶有清單的類型參數時,這似乎失敗了。副本失去了所有參數類型的知識。Scala:如何創建案例級副本保留清單信息

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" 

爲什麼Foo.copy失去了清單上的參數,如何讓它保留呢?

回答

15

幾個斯卡拉特性相互作用,給這種行爲。首先,Manifest不僅附加到構造函數的隱式隱式參數列表中,還附加在copy方法上。衆所周知的是

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

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

只是語法糖,但這也影響了拷貝構造函數,它看起來像這樣

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

所有這些implicit m s都是由th創建的e編譯器並通過隱式參數列表發送給該方法。

只要有人在編譯器知道Foo的類型參數的地方使用copy方法,就可以。例如,這將工作外的酒吧類:

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

這工作,因爲編譯器推斷fooFoo[Int]所以它知道foo.aInt所以它可以調用copy這樣的:

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

(注意manifest[T]()是創建類型T,例如Manifest[T]以大寫「M」的清單表示的功能。未示出的是一個將默認參數添加到copy中。)它也適用於Foo類,因爲它已經具有在創建類時傳入的清單。這將是這個樣子:

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" 

在最初的例子,但是,它無法在Bar類,因爲第二個特點的:而Bar類型參數的Bar類中已知的類型,參數類型參數不是。它知道在Bar中的AFooSubFooSubSubFoo,但是如果它是Foo[Int]Foo[String]則不是。當然,這是Scala中衆所周知的類型擦除問題,但即使看起來類似於類型foo的類型參數,也會出現問題。但是,請記住,每次調用copy時都會有一個祕密注入清單,這些清單會覆蓋之前存在的清單。由於Bar類沒有想法的foo類型參數,它只是創造了Any清單,並將沿着這樣的:

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

如果一個人在Foo類控制(例如,它不List),那麼一個變通辦法,通過添加一個方法在做的所有複製的Foo類,將做適當的複製,像localCopy以上,並返回結果:

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" 

的另一種解決方案是增加Foo S型參數作爲Bar一個顯現的類型參數:

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

但這鱗如果不良的類層次結構是大的,(即更多的成員具有類型參數,而這些類也具有類型參數),因爲每個類都必須具有每個類下面的類型參數。這也似乎使類型推斷嚇壞了試圖建構當Bar

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

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

幹得好,我的朋友! – 2012-08-12 08:04:49

1

有你確定了兩個問題。第一個問題是Bar內部的類型擦除問題,其中Bar不知道Foo的清單類型。我個人會使用您建議的localCopy解決方法。

第二個問題是,另一個隱式被祕密注入到copy。通過明確地將該值傳遞給copy解決了該問題。例如:

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 

我們看到的副本,更讓Int清單但fooCopyres2類型爲Manifest[Any]由於擦除。

因爲我需要訪問隱式證據來做copy我不得不使用明確的implicit(hah)語法而不是上下文綁定語法。但使用明確的語法造成的錯誤:

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?上下文綁定語法如何工作以及顯式的implicit不是?我挖了一遍,發現了一個解決問題的辦法:@uncheckedVariance註解。

UPDATE

我周圍挖一些,發現在斯卡拉2.10 case類已更改爲從第一個參數列表中複製字段copy()

Martin說:case class ness只賦予第一個參數 list其餘的不應該被複制。

https://issues.scala-lang.org/browse/SI-5009查看此更改的詳細信息。

+0

我試圖做到這一點,但不知道'@ uncheckedVariance'。這是否會導致一些不合理的任務,或者僅僅是這樣一個事實,即在Scala中沒有單獨的協變和逆變「Manifest」的人爲因素? – drhagen 2012-08-14 12:36:33

+0

我認爲這只是一個神器。令我感到沮喪的是上下文綁定的語法工作,但明確的隱含語法沒有,所以我挖掘並發現'@ uncheckedVariance'。我的猜測是,上下文綁定語法基本上是在幕後執行'@ uncheckedVariance'。 – sourcedelica 2012-08-14 13:13:45

+0

在答案中增加了關於'@ uncheckedVariance'的信息。 – sourcedelica 2012-08-14 13:53:14