関数

1. 関数の基本

“関数”と”メソッド”の違いについて気になるのですが、まずは関数の基本から紹介します。 (Scala では”関数”もオブジェクト!です。最初、この意味が理解出来ませんでした) 以下は単純に引数の数値に5を足して結果を返す関数です。

def addFive(x:Int):Int = {
  x + 5
}

// 関数の呼び出し
addFive(4)
// 9

引数の型も戻り値の型も指定した関数です。 ちなみに最後に呼ばれた行が結果として返されます。returnは不要です。

def addFive(x:Int) = x + 5

このような形で、戻り値の型や “{“と”}“の省略が可能です。 また、結果を返さないprintln等の関数を呼んでから、結果を返す行も実行可能です(最終行がprintlnとかであれば戻り値無し)。

def addFive(x:Int) = {
  println("今から " + x + "に5を足します")
  x + 5
}

// 上の関数を呼ぶと・・・
addFive(4)
// "今から 4に5を足します"  と出力されて
// 9が戻り値として返されます。

2. 引数色々

引数の型の後ろにアスタリスクを指定する事で、複数個の引数を受け取る事ができます。 例では、複数の文字列を受け取って、出力しています。

def prints(lists: String*) = {
  for(item <- lists) println(item)
}

prints("これは","テスト","です。")
// これは
// テスト
// です。

関数の引数に名前を指定して渡すことができます。

def printDay(year:Int, month:Int, day:Int) = println(year + "/" + month + "/" + day)
// 普通の呼び出し
printDay(1988,9,12)
// 1988/9/12
// 引数の名前を指定して呼び出しても同じ結果
printDay(day=12,month = 9,year=1988)
// 1988/9/12

また、デフォルト値を指定する事も可能です。

def printTargetDay(year:Int = 1999, month:Int, day:Int) = println(year + "/" + month + "/" + day)
// year の指定は不要だが、他の月日は指定が必要
printTargetDay(month = 1, day = 2)
// 1999/1/2

3.関数リテラル

リテラルとは、プログラミングにおいて”1” や “hello”などの後から変更されない定数的なものの事です。 関数も『リテラル』として宣言出来るため値として『変数に代入』ができます。

(name : String) => println("Hello " + name)

この例が『関数リテラル』の簡単な例です(これだけだと実行できません。変数へ代入が必要)。 『(引数)』と『=>』と『式』 がセットで関数リテラルです。 例えば、この関数リテラルを変数に代入して、利用する例が以下の内容です。

var func1 = (name : String) => println("Hello " + name)
func1("Jon")
// Hello Jon

例えばこのようにListクラスのforeachの引数に関数リテラルを渡して処理する事ができます。

val nameList = List("Jon","Bob","Meary")
nameList.foreach((name : String) => println("Hello " + name))
// Hello Jon
// Hello Bob
// Hello Meary

とりえあず『=>』が出てくれば『関数リテラル』だと思って下さい。

4.関数リテラル 『戻り値』

前述の様に、関数リテラルを変数に代入して利用する事も可能ですが、『戻り値』として関数リテラルを定義可能です。 『引数が文字列型で、戻り値がInt型の関数』を返す関数が定義できます。

def returnFunc(): (String) => Int = {
   (param:String) =>
    {
     println(param)
     1
    }
 }

一見読みにくく感じるのですが、素直に
『:』の後の『(String) => Int』が戻り値である『関数リテラル』

『(param:String) => ・・・ 1 } 』が実態と言うようにわけてみると読みやすくなります。
『=>』の左辺と右辺ごとのブロックを見るようなイメージです。
実際にこの関数を呼ぶ際には以下のようになります。

// returnFuncを呼んで戻り値である『関数』を別の変数に代入
val callForFunc = returnFunc
// 『関数』をパラメータ "Hello Scala"で呼ぶ
val iResult = callForFunc("Hello Scala")
// Hello Scala
// 文字列が出力されてiResultには1が設定される。

5.クロージャ(closure)

クロージャとは『関数』の一種の事です。初めてクロージャを見たときは何がなんだかよくわからなく、『ただ、内側の関数の外にある変数を保持して処理』できてるだけで、何がクロージャなのか理解できませんでした。

object Man {
  def callChild():(String) => Unit = {
   var iCounter = -1
   (name:String) => {
     for(i <- 0 to iCounter) print("更に、")
     println(s" 大きな声で $name を呼ぶ")
     iCounter = iCounter+1
    }
  }
}

callChild() は『関数オブジェクト』を返します。 callChild()が呼ばれた時点では、文字列も出力されません。

val call = Man.callChild()

上記の変数 『call 』には、関数オブジェクトが代入されています。

call("Jon")
// 大きな声で Jon を呼ぶ

単純に『大きな声でJonを呼ぶ』が出力されます。 但し、call の中の関数オブジェクトの iCounter は+1されて状態が保存されています。

call("Jon")
// 更に、 大きな声で Jon を呼ぶ

iCounter は 0 の状態で、callの内部の関数リテラルが呼ばれて、『更に』が一度だけ出力されます。

call("Jon")
// 更に、更に、 大きな声で Jon を呼ぶ

呼ばれるたびに、iCounterは+1されて、『更に』が何度も呼ばれることになります。 このように関数オブジェクト外部の変数(iCounter)を参照して利用できる事をScalaではクロージャと呼んでいます。 更に、この例では関数オブジェクトを変数に代入して、iCounterを変数代入時に初期化してから、 関数リテラルを何度か呼び出す際にも状態を保持する例を紹介しました。 このような状態保持の目的で利用される場面が多いようです。