例えばこういうの。
scala> case class Hoge(name: String) extends AnyVal
defined class Hoge
scala> def hoge(f: String => Hoge): Hoge = f("hoge")
hoge: (f: String => Hoge)Hoge
scala> hoge(Hoge)
res1: Hoge = Hoge(hoge)
hoge
メソッドの引数は f: String => Hoge
、すなわち「String をとって Hoge を返す関数」であるが、
hoge(Hoge)
と case class を指定しても正しく動く。最初はおやっと思いました。
scalaのeta-expansionってなんなのかようやくわかった | KentaKomai Blog
メソッドから FunctionN への変換、これを eta-expansion というらしい。
FunctionN が型として要求される箇所にメソッドを書いても同じように eta expansion される。
FunctionN が型として要求される箇所にメソッドを書いたら、この eta-expansion が自動的に行われるらしい。
最初の例に戻ると、
hoge(Hoge)
は、メソッド引数で FunctionN が型として要求される箇所に case class を渡しているように見えるが、
case class を渡しているのではなく、case class の apply メソッドを渡している形になる。
この case class の apply メソッドが eta-expansion されて、String => Hoge
な FunctionN になっているので正しく動くということみたい。
apply を明示的に書いてやってみると同じ結果になる。
scala> hoge(Hoge.apply)
res2: Hoge = Hoge(hoge)
番外編: メソッドオーバーロードとeta-expansion
#引数オーバーロードのデメリット ##(多相のままでは)eta-expansionできない これにより関数合成、変換、など様々な操作ができなくなる
オーバーロードされたメソッドをeta-expansionする - kmizuの日記
object O {
def add(x: Double, y: Int): Double = x + y
def add(x: Int, y: Double): Double = x + y
}
O.add _:((Double, Int) => Double)
以前 Scala 界隈の強い人から「オーバーロードはいろいろあるから推奨しない、別メソッドにしたほうがいい」的なことを聞いたことがあり、なんでかなと思ってたけどこういうことが理由なのかな?