2012-08-11 104 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