型パラメータをとらないcase classのコンパニオンはFunctionを継承する

知っててもそれほど頻繁には役にたたない(?)、トリビア的なもの。説明するよりコード示したほうがわかりやすい気がするので、まずはコード

Welcome to Scala version 2.10.3 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_45).
Type in expressions to have them evaluated.
Type :help for more information.

scala> case class Foo(a: Int, b: String)
defined class Foo

scala> Foo.getClass.getSuperclass
res0: Class[?0] forSome { type ?0 >: ?0; type ?0 <: Foo.type } = class scala.runtime.AbstractFunction2

scala> val f: (Int, String) => Foo = Foo
f: (Int, String) => Foo = Foo

scala> Foo eq f
res1: Boolean = true

つまり、コンパニオンに以下のようなものが勝手に実装されるということです

case class Foo(a: Int, b: String)

object Foo extends ((Int, String) => Foo){
  override def apply(v1: Int, v2: String): Foo = new Foo(v1, v2)
}

もちろん、「パラメータが2つのものがFunction2になる」というのに限らず、パラメータ1つでも、パラメータ3つでも同様*1です。
getClass.getSuperclassもしくはgetClass.getInterfacesをしてみるとわかりますが、自分が調べた限り少なくともScala2.7.7のころからずっと*2そういう仕様みたいです。
しかし、現状の仕様書を見てみたところ、この件に関する記述見当たりませんでした。*3
あと、本とかWebの資料でもこれに言及しているものあまり見たこと無いんですが、なんででしょうね*4
メーリングリストや、ScalaのJIRAでコミッターの人が話してるのは見たことありますが。


これ知ってて何に役に立つの?と言われると微妙なのですが。たとえば、以下のようにプレースホルダー使ってわざわざ関数オブジェクトの生成をやり直したら、無駄なオブジェクト生成されて勿体無いという話です。

scala> val f = Foo(_, _)
f: (Int, String) => Foo = <function2>

あと標題の、「型パラメータをとらない」ですが、ScalaのFunctionオブジェクトは、trait自体は型パラメータをとりますが、それぞれのFunctionNのapplyメソッドは型パラメータをとりません。なので、case class自体が型パラメータを取る場合*5は、そのコンパニオンがFunctionNを継承するのは不可能、という単純な(?)理由です。






ここからはだいぶ話変わるというか余談ですが

object creation impossible, since method apply in trait Function3 of type (中略) is not defined

という、case class定義しただけなのに、関係なさそうな一見謎なエラーメッセージに遭遇したのは
https://github.com/xuwei-k/scalikejdbc/commit/1429f9181d9#commitcomment-4800974

今まで説明したような「コンパニオンがFunctionNを継承する」という実装が、変な形で露呈してしまったという感じです。「コンパイルエラーにするべき」もしくは「コンパイル通るべきなのに、コンパイルエラーになってしまっている」けど、どちらにしろエラーメッセージがおかしいという感じかなぁ

*1:ただし型パラメータをとらないものに限る

*2:2.8でも2.9でも、2.11.0-M7でも

*3:自分が見逃してるだけで、もし書いてあったら教えて下さい

*4:自分が知らないだけか?あったら教えて下さい

*5:たとえば、case class Bar[A](value: A) のようなもの