minimize

事業拡大のため、新しい仲間を募集しています。
→詳しくはこちら

Currying

Scala では、一風変わった引数の取り方をする関数を定義できる。

def sum(f: Int => Int): (Int, Int) => Int = {
  def sumF(a: Int, b: Int): Int =
    if (a > b) 0 else f(a) + sumF(a + 1, b)
  sumF
}

この sum という関数は f を引数に取る。f は、数値を数値に変換する関数。
sum の戻り値は「関数」だ。
ここで、sum を使った別の関数を定義してみよう。

def sumSquares = sum(x => x * x)

これは一種の関数エイリアスだと考えるとわかりやすい。
上の例だと、f は x => x * x となる。
つまり展開すればこうだ。

def sumSquares : (Int, Int) => Int = {
  def sumF(a: Int, b: Int): Int =
    if (a > b) 0 else (a * a) + sumF(a + 1, b)
  sumF
}

sumSquares という関数は、引数を取らずに関数 sumF を返す。
これをどうやって使うのか?次のようにする。

sumSquares(1, 10)

さっき「sumSquares は引数を取らないって言ったのに」と思う方もいるだろう。
しかし間違えてはいない。sumSquares は引数を取らないのだ。
その代わり、sumSquares は関数を返す。
つまり、sumSquares が返した関数に対して (1, 10) という引数を適用(apply)している訳だ。

sumSquares.apply(1, 10)

わかりやすく展開すれば以下のようになる。

def sumF(a: Int, b: Int): Int =
  if (a > b) 0 else (a * a) + sumF(a + 1, b)
sumF(1, 10)

これで随分理解しやすくなっただろう。
つまり、sumSquares(1, 10) は 1 から 10 までの数を二乗した合計値を返す。

数学的な話になるが、f(args1)(args2) は、(f(args1))(args2) と等しい。
僕は数学科出身ではないので詳しい理由は知らない。(←でも大学で確か勉強したハズだ)

Curried function

結論から言えば、先ほどの sum は以下のように書き直せる。

def sum(f: Int => Int)(a: Int, b: Int): Int =
  if (a > b) 0 else f(a) + sum(f)(a + 1, b)

まぁ何となくは理解できると思う。このような記述法を Curried function と呼ぶ。
ところで、

def sum(f: Int => Int, a: Int, b: Int): Int =
  if (a > b) 0 else f(a) + sum(f, a + 1, b)

↑こちらと何が違うのかと問われれば、何も違わない。
どちらでも処理結果は一緒だ。
じゃあなぜこんな記述法を使うかと言えば、最初に紹介した sumSquares で比べてみればわかる。

curried function で sum を定義した場合、sumSquares を以下のように定義できた。

def sumSquares = sum(x => x * x)

しかし通常の記法で sum を定義すると、sumSquares の定義は以下のようになる。

def sumSquares(a: Int, b: Int): Int = sum(x => x * x, a, b)

少しだが、こちらの方が冗長だ。

もちろん、Curring の真の目的は「関数エイリアスで冗長さを無くす」というようなちっぽけなものではない。
関数の結果に関数を適用するという手法。
これこそまさに関数型言語といった使い方と言えるだろう。