カリー化


関数型言語ではカリー化というテクニックがよく使われます。たとえば、(Int, Int) => Int 型の関数のように複数の引数を取る関数があったとき、 これを Int => Int => Int 型の関数のように、1つの引数を取り、残りの引数を取る関数を返す関数のチェインで表現するというものです。

ためにしaddをカリー化してみます。

val addCurried = (x: Int) => ((y: Int) => x + y)

無名関数を定義する構文をネストさせて使用しているだけです。
Scalaではメソッドの引数リストを複数に分けた場合に、その引数リストそれぞれを新たな引数としたカリー化関数を得ることができます。

メソッドと関数の違い 本来はdefで始まる構文で定義されたものだけがメソッドですが、説明の便宜上、所属するオブジェクトの無いメソッドなどを関数と呼んだりすることがあります。
「Scalaスケーラブルプログラミング」(コップ本)でも意図的に関数とメソッドの混同例がいくつかあります。注意してください。

  • メソッドはdefで始まる構文で定義されたものです。これを関数と呼ぶのはあくまで説明の便宜上です。
  • メソッドと関数の違いは”第一級の値”であるか否かです。
  • メソッドは第一級の値では無いのに対して関数は第一級の値であるという大きな違いがあります。
  • メソッドを取る引数やメソッドを返す関数、メソッドが入った変数というものはScalaには存在しません。

関数を引数に取ったり関数を返すメソッドや関数のことを高階関数(High Order Function)と呼びます。
メソッドのことも関数と呼ぶのは奇妙ですが慣習(因習)です。慣れてください。
高階関数の例を示します。

def double(n: Int, f: Int => Int): Int = {
  f(f(n) )
}

これは与えられた関数fを2回nに適用する関数doubleです。ちなみに、高階関数に渡される関数は適切な名前が付けられないことが多く、その場合はfやgなどの1文字の名前をよく使います。 他の関数型プログラミング言語でも同様の習慣があります。

もう少し意味のある例を出してみます。プログラムを書くとき

  1. 初期化
  2. 何らかの処理
  3. 後始末

というパターンは頻出します。これをメソッドにした高階関数aroundを定義します。

def around(init: () => Unit, body: () => Any, fin: () => Unit): Any = {
  init()
  try {
    body()
  } finally {
    fin()
  }
}

try構文はだいたいJavaと一緒です。

() => は引数が無いことを示しています。

このようにそれぞれを部品化して「何らかの処理」の部分で異常が発生しても必ず後始末処理を実行できます。このaroundメソッドは1~3の手順を踏む様々な処理に流用できます。

一方1~3の処理にはそれぞれ呼び出し側で自由に与えることができます。このようい処理を値として部品化することは高階関数を定義する大きなメリットの1つです。

なおaroundのように高階関数を利用してリソースの後始末を行うパターンはローンパターンと呼ばれています。

各関数の呼び出し例

def main(args: Array[String]): Unit = {
  println(add.apply(10, 20))
  println(add(30, 40))

  println(add2(50, 60))
  println(addCurried(70)(80))

  //高階関数 doubleの呼び出し
  println(double(1, m => m * 2) )
  println(double(2, m => m * 3) )
  println(double(3, m => m * 4) )

  //高階関数 aroundの呼び出し
  around(
    () => println("File Open"),
    () => println("File Process"),
    //() => throw new Exception("Illegal Error!"),
    //例外を発生させてもfinの部分はちゃんと動作しています。
    () => println("File Close")
  )

}