関数のカリー化も式の置き換え

Haskell の関数のカリー化も便利な機能だ。関数のカリー化を汎用的にきちんと定義するのは教科書に任せるとして、2変数の関数を例として考えてみよう。二つの引数 x と y をとり、その和の値を返す add x y という関数を考える。add x y の定義は次のようになる。

add x y = x + y

x = 1, y = 2 の場合、add 1 2 = 3 である。一方この関数は、引数 x をとり、関数 x + y を返す関数と考えることができる。つまり、x = 1 のとき、add 1 y = 1 + y という関数が返されると考えることができる。add 1 y は、1引数 y をとる関数であるから、(add 1) 2 = 3, (add 1) 4 = 5 などのように関数として使うことができる。また、addOne という新しい関数を次のように定義することもできる。

addOne = add 1

この関数に引数を与えると、addOne 2 -> 3, addOne 5 -> 6 のように数値の戻り値が返される。Hugs で実行すると次のようになる。

Hugs> addOne 2 where add x y = x + y; addOne = add 1
3

カリー化のできる関数では、2変数の関数から1変数の関数を作り出したり、3変数の関数から2変数や1変数の関数を作り出したりすることができる。カリー化は関数だけでなく演算子にも使えるので、

inc = (+1) や dec = (-1)

のような定義もすることができる。

この関数のカリー化がどのように動作するかも、式の置き換え、展開で説明することができる。例えば、上の例の addOne のようなものも次のように展開して動作を理解できる。

addOne 2
= (add 1) 2
= add 1 2
= 1 + 2
= 3

このように、Haskell のプログラムの動作は、式の展開で理解することができるが、これは、Haskell のような関数型のプログラム言語が、λ記法の理論をもとに設計されているからだ。λ記法を使うと、関数の定義を全て記号の置き換えだけで行うことができる。関数の実装のことは一切考えず、形式的な記号の置き換えだけで関数を定義することができるのだ。

もちろん、λ記法で全ての関数が定義できる訳ではなく、決定不能な関数は定義できない。しかし、ノイマン型コンピュータで計算可能な物は全てλ記法で記述できるので、理論上はコンピュータがやれる全てのことはλ記法で記述できることになる。

話が脱線するが、Haskell を使っていてうれしいのは関数を引数にとったり、戻り値として返したりする高階関数が簡単に利用できることだ。たとえば、map という関数は、関数一つと、リスト一つを引数にとり、リストの要素一つ一つにその関数を適用させたリストを返す。

Hugs> map (\x -> x * x) [1,2,3]
[1,4,9]

このような高階関数の動作も、式の置き換えで説明できてしまう。これもλ記法のおかげなのだ。

コンピュータプログラムと言うと、グラフを表示したり、データベースを操作したり、ウエブのページを自動作成したりというイメージがあるので、入出力をモナドに閉じ込めてしまった Haskell は、「使えない」という印象を受けるが、プログラムの範囲を Hugs 上で実行できる数値やリストの処理だけに絞って利用していると、Haskell の簡潔さと、その簡潔さ故のパワーを感じられて魅力を感じるだろう。

将来的には、関数をHaskellで記述して、その関数を他の手続き型のプログラムから利用するというようなインターフェースができれば便利かもしれない。
[PR]
by tnomura9 | 2010-10-11 08:58 | Haskell | Comments(2)
Commented by sugi51 at 2011-06-13 05:39 x
すごい分かりやすいです。助かりました。ありがとうございます。
Commented by tnomura9 at 2011-06-13 23:47
sugi51さん、コメントありがとうございました。
Haskell でプログラムすると、どうしても暗号じみた文になりがちですが、数式を展開するのと同じ要領で展開すると意味が分かるようです。記事を喜んでいただいて、うれしいです。ありがとうございました。
<< Haskell の手続き言語もどき foldr と foldl >>