5.2 カリー化 (Currying)

和を求める関数の最後の設計はすでにかなり簡潔です。しかし更に可能です。a と b がすべての関数でパラメータまたは引数として現れる一方で、興味深い組み合わせを成していないようです。それらを除去できるでしょうか。

sum を書き直し、境界 a と b をパラメータにしないようにしましょう。

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 は他の関数、すなわち特別な和を求める関数 sumF を返す関数です。後者の関数がすべての仕事をします。境界 a と b をパラメータとしてとり、sum 関数のパラメータ f をその間のすべての整数に適用し、結果を足し合わせます。

新設計の sum を使って、次のように定義できます。

def sumInts = sum(x => x) def sumSquares = sum(x => x * x) def sumPowersOfTwo = sum(powerOfTwo)

Or, equivalently, with value definitions:

あるいは同等に、値定義を使って

val sumInts = sum(x => x) val sumSquares = sum(x => x * x) val sumPowersOfTwo = sum(powerOfTwo)

sumInts, sumSquares, sumPowersOfTwo は、他の関数と同じように適用できます。たとえば

scala> sumSquares(1, 10) + sumPowersOfTwo(10, 20) unnamed0: Int = 267632001

関数を返す関数はどのように適用されるのでしょうか?例として式

sum(x => x * x)(1, 10) ,

の関数 sum は2乗する関数 (x => x * x) に適用されます。そして結果の関数は二番目の引数リスト (1, 10) に適用されます。

関数適用が左結合のため、この記法が可能です。すなわち、もし args1 と args2 が引数リストなら、

f(args 1)(args2) は (f(args1))(args2) と等価です。

この例では sum(x => x * x)(1, 10) は、式 (sum(x => x * x))(1, 10) と等価です。

関数を返す関数のスタイルはたいへん有用なので、Scala にはそのための特別な構文があります。たとえば次の sum 定義は前のものと等価ですが短くなっています。

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

一般に、カリー化された関数定義

def f (args1) ... (argsn) = E

ただし n>1、は次のように展開されます。

def f (args1) ... (argsn−1) = { def g (argsn) = E ; g }

ただし g は未使用の識別子です。あるいはもっと短く無名関数を使って

def f (args1) ... (argsn−1) = ( argsn) => E .

このステップを n 回繰り返すと次を得ます。

def f (args1) ... (argsn) = E

これは次と等価です。

def f = (args1) => ... => (argsn) => E .

あるいは同様に値定義を用いて

val f = (args) => ... => (args) => E .

この関数定義と適用のスタイルは、創始者である20世紀の論理学者 Haskell B. Curry に因んで カリー化 と呼ばれています。しかしそのアイデアは Moses Schoenfinkel や Gottlob Frege にまで遡ります。

関数を返す関数の型は、パラメータリストに似た形で表されます。例として sum の最後の設計を見ると、sum の型は (Int => Int) => (Int, Int) => Int です。関数型は右結合なため、これが可能です。すなわち

T1 => T2 => T3 は T1 => (T2 => T3) と等価

演習 5.2.1 関数 sum は線形再帰を使ってます。?? の部分を埋めて末尾再帰に書けますか?

def sum(f: Int => Double)(a: Int, b: Int): Double = { def iter(a, result) = { if (??) ?? else iter(??, ??) } iter(??, ??) }

演習 5.2.2 与えられた範囲の点の、関数値の積を求める関数 product を書きなさい。

演習 5.2.3 factorial を product を用いて書きなさい。

演習 5.2.4 sum と product の両方を一般化する、より汎用的な関数を書けますか?