確認してみたのでメモ。
結論
def内にてdefによるローカル関数として宣言すると評価は1回だけだが、関数リテラルとして宣言すると外側のdefが実行されるたびに都度評価されてしまう模様。
※「評価」という言葉が適切じゃない気がする。単に「生成」かな。
確認
ローカル関数の場合
def foo(n: Int) = {
def localFunc(a: Int)(b: Int) = a + b
localFunc(1)(n) + localFunc(2)(n)
}
(1 to 10).foreach(n => println(foo(n)))
このプログラムを以下のコマンドで実行して確認してみる。
> scalac src/main/scala/Main.scala -Xprint:jvm
するとこう出力される(fooメソッド部分のみ抜粋)。
localFuncはfooの外側で宣言されたように扱われている。
def foo(n: Int): Int = Main.this.localFunc$1(1, n).+(Main.this.localFunc$1(2, n));
final private[this] def localFunc$1(a: Int, b: Int): Int = a.+(b);
関数リテラルの場合
def foo(n: Int) = {
val localFunc: Int => Int => Int = a => b => a + b
localFunc(1)(n) + localFunc(2)(n)
}
(1 to 10).foreach(n => println(foo(n)))
関数リテラルで宣言した場合は以下のようになり、fooの呼び出しの度にnewが行われてしまうようだ。
def foo(n: Int): Int = {
val localFunc: Function1 = {
(new <$anon: Function1>(): Function1)
};
localFunc.apply(scala.Int.box(1)).$asInstanceOf[Function1]().apply$mcII$sp(n).+(localFunc.apply(scala.Int.box(2)).$asInstanceOf[Function1]().apply$mcII$sp(n))
};
おまけ(ローカル関数を部分適用する)
こんな感じで部分適用した関数を宣言しても同様にfooの呼び出しの度にnewされる。
def foo(n: Int) = {
def localFunc(a: Int)(b: Int) = a + b
val f1 = localFunc(1) _
val f2 = localFunc(2) _
f1(n) + f2(n)
}
(1 to 10).foreach(n => println(foo(n)))
結果はこう。
localFuncは無事外で宣言されているもののf1とf2はfoo内。
def foo(n: Int): Int = {
val f1: Function1 = {
{
(new <$anon: Function1>(): Function1)
}
};
val f2: Function1 = {
{
(new <$anon: Function1>(): Function1)
}
};
f1.apply$mcII$sp(n).+(f2.apply$mcII$sp(n))
};
final def localFunc$1(a: Int, b: Int): Int = a.+(b);
というわけでこれを避けるにはどうするか?
単純にfooメソッドの外で宣言すればよい。
private val (f1, f2) = {
def localFunc(a: Int)(b: Int) = a + b
(localFunc(1) _, localFunc(2) _)
}
def foo(n: Int) = f1(n) + f2(n)
(1 to 10).foreach(n => println(foo(n)))
しかし本来はfooメソッドの中でしか使わないからこそfoo内で宣言したという経緯があるはずで、privateが付いているとはいえf1とf2をfooのスコープ外で宣言するというのはちょっと悔しい感がある。
というわけでこのように書いてみた。
val foo: Int => Int = {
def localFunc(a: Int)(b: Int) = a + b
val f1 = localFunc(1) _
val f2 = localFunc(2) _
(n:Int) => f1(n) + f2(n)
}
(1 to 10).foreach(n => println(foo(n)))