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 の両方を一般化する、より汎用的な関数を書けますか?

Comments