为什么case类的伴生对象要扩展FunctionN?

38

当你创建一个 case class 时,编译器会自动生成一个相应的伴生对象,其中包含一些 case class 特性:apply 工厂方法匹配主构造函数、equalshashCodecopy

有点奇怪的是,这个生成的对象扩展了 FunctionN。

scala> case class A(a: Int)                                 
defined class A

scala> A: (Int => A)
res0: (Int) => A = <function1>

仅当以下条件同时满足时才适用:

  • 没有手动定义的伴生对象
  • 恰好有一个参数列表
  • 没有类型参数
  • 这个案例类不是抽象的。

看起来这是大约两年前 添加的。最新版本在这里

有人使用过此功能,或者知道为什么要添加它吗?它会通过静态转发方法稍微增加生成的字节码大小,并出现在伴生对象的#toString()方法中:

scala> case class A()
defined class A

scala> A.toString
res12: java.lang.String = <function0>

更新

手动创建只有一个apply方法的对象不会自动被视为FunctionN

object HasApply {
  def apply(a: Int) = 1
}
val i = HasApply(1)

// fails
//  HasApply: (Int => Int) 

4
这是个好问题。也许这样做是为了能够写出类似于:“List(1,2) map A”的代码?我有时会用到这个。 - Mirko Stocker
4个回答

48

case class(案例类)伴生对象实现FunctionN的原因是,在此之前,case class生成了一个类和一个工厂方法,而不是一个伴生对象。当我们在Scala中添加提取器时,将工厂方法转换为具有apply和unapply方法的完整伴生对象更加合理。但是,由于工厂方法符合FunctionN,因此伴生对象也需要符合。

[编辑] 话虽如此,让伴生对象显示其自身名称而不是"function"更有意义。


1
啊,向后兼容性,再加上统一访问原则的帮助。在那种情况下,这是有道理的。我已经提交了一个增强请求来美化这些对象的 toString 方法:https://lampsvn.epfl.ch/trac/scala/ticket/3579 - retronym

11

考虑到在Scala中target.apply(a1, a2, a3 ... aN):

  1. 可以简化为target(a1, a2, a3 ... aN)
  2. 是需要由FunctionN实现的方法

因此,一个伴生对象似乎很自然:

object MyClass {
  def apply(a1 : A1, ... aN: AN) = new MyClass(a1, ..., aN)
}

真正的含义是:

object MyClass extends FunctionN[A1, ... , AN, MyClass]{
  def apply(a1 : A1, ... aN: AN) = new MyClass(a1, ..., aN)
}

所以对我来说,这个加法似乎很自然(我不确定为什么你觉得它“奇怪”?)。至于它是否真正地增加了任何东西;好吧,那是留给比我更聪明的人!


11

除了oxbow_lakes关于此的回答强调它的自然性外,将构造函数作为一流函数可经常与Scala集合高阶函数一起使用,这通常非常有用。以(一个微不足道的)例子为例:

scala> case class Foo(i : Int)
defined class Foo

scala> List(1, 2, 3) map Foo   
res0: List[Foo] = List(Foo(1), Foo(2), Foo(3))

3
xs map Foo.apply 的便利程度只是稍微低一些。 - retronym

5
Welcome to Scala version 2.8.0.RC3 (Java HotSpot(TM) Client VM, Java 1.6.0_20).

scala> case class CC3(i: Int, b: Boolean, s: String)
defined class CC3

scala> CC3
res0: CC3.type = <function3>

scala> CC3.apply(1, true, "boo!")
res1: CC3 = CC3(1,true,boo!)

scala> CC3(1, true, "boo!")
res2: CC3 = CC3(1,true,boo!)

2
无论接收器是否扩展FunctionN,省略apply名称的语法糖都是可用的。 - retronym

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接