scalaのFunctionについて説明してみるよ

ちょっと気になったことがあったので、(人のblogの記述をもとに)ついでに色々説明してしまう感じ(`・ω・´)

引用↓

Scala(省略なし)
def sum(i: Int, j: Int): Int = {
return i + j
}

ScalaRubyと同じでreturnは不要)
def sum(i: Int, j: Int): Int = {
i + j
}

Scala(戻り値も型推論可能)
def sum(i: Int, j: Int) = {
i + j
}

Scala(1行の場合は括弧不要)
def sum(i: Int, j: Int) = i + j

Scala(引数を省略した場合)
def sum = (_: Int) + (_: Int)

ScalaとJavaの相違点の箇条書き : mwSoft blog

Scalaのメソッドの書き方の例が4点でてきていますが、この中で一番最後だけちょっと意味違うよっていう話。

そのまえに

1行の場合は括弧不要

でなく正確には、「1つの式として解釈出来る場合」だお(´・ω・`)という細かいツッコミもしつつ。

もとのやつ書いた方がこの違いわかっていてわざと細かいところ省略して、「こういう風にも書けるよー」という感じで書いたのか、若干勘違いしていたのかはまぁ置いておいてとにかく説明します(`・ω・´)

最後のものをREPLに打ち込むともちろんコンパイル通って以下のような表示になります。

scala> def sum = (_: Int) + (_: Int)
sum: (Int, Int) => Int

そしてもちろん以下のような使い方できます

sum(1,2)
res0 : Int = 3

がしかし、上記のsumの定義は「 "Intを2つ引数にとって、Intを返すメソッド" の定義 」ではありません。


なぜそうじゃないかっていうと、以下のように戻り値の型に明示的にIntを書いてみると、コンパイルエラーになります。

scala> def sum:Int = (_: Int) + (_: Int)
<console>:7: error: type mismatch;
 found   : (Int, Int) => Int
 required: Int
       def sum:Int = (_: Int) + (_: Int)
                              ^

型があってないって言われます(´・ω・`)


required:Int っていうのは、明示的に Int が戻り値の型だよと自分で書いたからで、しかし
found : (Int, Int) => Int というのは、「Intを2つ引数にとって、Intを返す関数」が実際のメソッドの本体の式の型だとコンパイラさんは言ってます。


つまり戻り値の型を書くとしたら以下のようになります。

scala> def sum:Function2[Int,Int,Int] = (_: Int) + (_: Int)
sum: (Int, Int) => Int

//こっちでも同じ意味
scala> def sum:(Int,Int) => Int = (_: Int) + (_: Int)
sum: (Int, Int) => Int

つまり
Intを2つ引数にとって、Intを返すメソッドの定義
ではなく
「Intを2つ引数にとって、Intを返す関数」を返すメソッドの定義
です


それで、この書き方

def sum = (_: Int) + (_: Int)

がちょっとした問題があるというか、自分だったら実際この書き方することはないなーというのが言いたいことです。
このsumの定義は、以下のような記述と同じ意味になります。

def sum :Function2[Int,Int,Int] = new Function2[Int,Int,Int]{
  def apply(x:Int,y:Int):Int = {
    x + y
  }
}

Function2というのは、Scalaの標準ライブラリにあるtraitです。つまりこれ
sumというメソッドが呼ばれるたびに、毎回Function2の無名クラスのインスタンスを生成します!
その「毎回生成する」っていうのが無駄だから

val sum = (_: Int) + (_: Int)

でいいんじゃなイカ?(´・ω・`)ということです。


毎回生成されているかどうかは、例えば以下のようなことしてみるとわかります。
( sum というFunction2を返すメソッドを2回呼び出した結果のオブジェクトが、同一のもの*1かどうかを判定 )

scala> def sum = (_: Int) + (_: Int)
sum: (Int, Int) => Int

scala> sum eq sum
res1: Boolean = false

*1: scalaでのeqとは、Javaでの == にあたるもので、equals呼び出しではなく、参照が同じかどうかのみを判定