「カリー化」は元々は, 複数引数を取る関数を, 「1つだけ引数を取り関数を返す関数」の連鎖で統一的に表現する記述方法の事を言う.
三つの引数を取りその和を返す関数 f を (ある型 a に対して)「(a, a, a) → a」という型を持つ関数「f (x, y, z) = x + y + z」と表現するか, 「a → (a → (a → a))」(あるいは ‘→’ は右結合であると約束して「a → a → a → a」) という型を持つ関数「f x y z = x + y + z」として表現するかの二つの表現方法について後者の事を「カリー化」された記述と呼ぶ.
実際の処理内容の違いを直接示す訳ではないし, なんらかの変換手続きの事でもない.
ところが, プログラミング言語を特定すると「書き方」が決められてくる. なんとなれば, プログラミング言語とは, ある処理をどう書くかを規定するものだからだ.
C言語で
int f(int x, int y, int z) { return x + y + z;}
という関数 f の定義があったとして「f(3) と書くと, 『二つ引数を取り, 3 とそれらの総和を取る関数』を意味する」というようなことはない. コンパイラが構文エラーを吐くか, コンパイラのエラーを無視して, スタックに 3 を積んで, 関数 f のアドレスへジャンプしても, スタックの不整合が起こるのみである.
Haskell や ML では, 内部構文がカリー化されている. Haskell で
f x y z = x + y + z
のように, 「三つの引数を取り, それらの総和を返す関数」f を定義をした場合, f 3 は「二つの引数を取り, 3 とそれらの総和を返す関数」であるし, f 3 4 は「一つの引数を取り, 3, 4 およびその引数の総和を返す関数」であり, f 3 4 5 は「3, 4, 5 の和を返す関数」である.
より正確に言うと, f は【引数を一つ取り x をその値に束縛し『引数を一つ取り y をその値に束縛し「引数を一つとり, x と y とその数の和を返す関数」を返す関数』を返す関数】であり,
Haskell での型宣言 f :: a -> a -> a-> a は, そのことを示している.
そもそも, 複数引数を取る関数が定義できているかのように記述できることの方が構文糖衣 (内部的には, もっと基本的な別の構文に変換されるような, コードを見やすくするために提供される書き方のバリエーション) なのである.
たとえば, 「二引数を取り値を返す関数」を要求する高階関数への引数として f 3 を渡すことができる. Haskell インタプリタ GHCi で確認してみよう.
Prelude> let f x y z = x + y + z -- 3引数の和を返す関数 f を定義
Prelude> :t f -- f の型を確認
f :: (Num a) => a -> a -> a -> a -- Num に属する任意の型 (仮に a とする) について
-- a 型の引数を一つ取り,
-- a 型の引数を一つ取り,
-- a 型の引数を一つ取り a 型の値を返す関数
-- を返す関数
-- を返す関数
-- 言い換えると,
-- 三つの引数を取り, 一つの値を返す関数.
-- になっている.
Prelude> :t (f 3) -- f 3 の型を確認
(f 3) :: (Num t) => t -> t -> t -- Num に属する任意の型 (仮に t とする) について
-- t 型の引数を一つ取り,
-- t 型の引数を一つ取り t 型の値を返す関数
-- を返す関数
-- 言い換えると,
-- 二つの引数を取り, 一つの値を返す関数.
-- になっている.
Prelude> :t zipWith -- zipWith の型を確認
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
-- 任意三つの型 (仮に, a, b, c とする) について
-- (a 型の引数を取り
-- b 型の引数を取り c 型の値を返す関数
-- を返す関数)
-- を引数に取り
-- a 型のリストを引数を取り
-- b 型のリストを引数に取り c 型のリストを返す関数
-- を返す関数
-- を返す関数
-- 言い換えると,
-- (二つの引数を取り, 値を返す関数) と, 二つのリストを引数に取り,
-- リストを返す関数.
-- となっている.
Prelude> zipWith (f 3) [5, 7, 11, 13] [17, 19, 23, 29]
[25,29,37,45] -- 引数を二つ取る関数 (f 3) に
-- 二つのリストの要素を順に与え,
-- 返り値を順に要素とするリストを得た.
このように最初からいくつ目かまでの引数を適用し, 残りの引数を取るような関数をオブジェクトとして扱うことを「関数の部分適用」という.
カリー化表現を全面的に採用している言語では, 部分適用した関数を作るために特別な処理は必要ないが, そうでなくて且つ部分適用をサポートする言語では, 関数と最初の引数を与えると, 残りの引数を取る関数に変換することができる.
この関数の変換を行う関数が curry という名前であることが多いが, curry が行うのは関数部分適用であって, 「カリー化」ではない.
紛らわしいことには, Haskell にも curry という関数があり, これは, 一つのペアを引数に取る関数を二引数関数(実際には一引数を取る関数を返す一引数の関数)に変換する.
元の意味はともかく, 引数を部分適用した関数を作成する関数が curry という名前であることが多く, この curry 関数がしてくれる変換を「カリー化 (currying)」と呼ぶ人も多いので, 別にそれでもいいんではないかとは思う.
いずれにせよ, プログラマにとっては引数を部分適用した関数が作成できてオブジェクトとして扱えるかどうか, その処理はどう書くか, が関係するのみだ.
高階関数(関数を引数に取ったり, 関数を返したりする関数)があるような言語, とりわけ関数が第一級のオブジェクト(値も関数も同じように変数に束縛できる)であるような言語では, curry という名前の関数がこの変換を行ってくれる.
追記:
scala だと, _ を使った表記で, 引数を部分的に適用した関数を表現したり,
scala> def func1(x:Int, y:Int, z:Int):Int = x + y + z
func1: (Int,Int,Int)Int
scala> func1(3, _:Int, _:Int)
res1: (Int, Int) => Int = <function>
そもそもカリー化された状態で関数を記述し, 最初から任意個までの引数を適用した関数を表現したり (最後にアンダーバー ‘_’ が要るけど) できるようだ.
scala> def func2(x:Int)(y:Int)(z:Int):Int = x + y + z
func2: (Int)(Int)(Int)Int
scala> func2(3)_
res2: (Int) => (Int) => Int = <function>
また, Function.curried(func1 _) で func2 と同じ型の関数を, Function.uncurried(func2 _) で func1 と同じ型の関数を作れるらしい. この命名は適切に思える.
Haskell の例と同じことをやろうとしたが, zipWith が無いので, zip して map したが, それだと (Int, Int) のペア一つを取る関数を要求してしまう. func1 も func2 も直接渡せない. なんか良い例題ないかな…
scala> List(5, 7, 11, 13) zip List(17, 19, 23, 29) map func2(3)_
<console>:6: error: type mismatch;
found : (Int) => (Int) => Int
required: ((Int, Int)) => ?
List(5, 7, 11, 13) zip List(17, 19, 23, 29) map func2(3)_
^
↓これでは部分適用の例題にならない…
scala> List(5, 7, 11, 13) zip List(17, 19, 23, 29) map {p => func2(3)(p._1)(p._2) }
res3: List[Int] = List(25, 29, 37, 45)