LoginSignup
3
2

More than 5 years have passed since last update.

ローカル関数として宣言した場合と関数リテラルとして宣言した場合の評価(生成)の違いについて

Posted at

確認してみたのでメモ。

結論

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)))
3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2