カリー化と部分適用という概念について簡潔に説明する。
カリー化 / currying
定義
関数 $f$ に対して、次のような関数 $g$ を $f$ の $X$ についてのカリー化という。
$$
f: (X \times Y) \to Z, \quad f(x, y) := z \\
g: X \to (Y \to Z), \quad g(x) := f(x, \cdot)
$$
例
数式
$$
f: (\mathbb{R} \times \mathbb{R}) \to \mathbb{R}, \quad f(x, y) := x - y \\
g: \mathbb{R} \to (\mathbb{R} \to \mathbb{R}), \quad g(x) := f(x, \cdot) \\
g(1)(2) = -1
$$
注:高校レベルまでの数学で関数を返す関数を扱うことはないので、 $f(x, \cdot)$ という表記や、 $g(1)(2)$ という表記が奇妙に映るかもしれないが、よく使われるものである。
Scheme
(define func (lambda (x y) (- x y)))
(define func_curried (lambda (x) (lambda (y) (func x y))))
((func_curried 1) 2) ; -1
Julia
func(x, y) = x - y
func_curried(x) = y -> func(x, y)
func_curried(1)(2) # -1
JavaScript
let func = (x, y) => x - y;
let func_curried = x => (y => func(x, y));
func_curried(1)(2); // -1
C++ (C++14以上)
auto func = [](auto x, auto y) {
return x - y;
};
auto func_curried = [](auto x) {
return [&](auto y) {
return func(x, y);
};
};
func_curried(1)(2); // -1
Go
func f(x int, y int) int {
return x - y
}
func f_curried(x int) func(int) int {
return func(y int) int {
return f(x, y)
}
}
f_curried(1)(2) // -1
部分適用 / partial application
定義
関数 $f$ に対して、次のような $g_y$ を $f$ の $Y$ についての部分適用という。
$$
f: (X \times Y) \to Z, \quad f(x, y) := z \\
g_y: X \to Z, \quad g_y(x) := f(x, y)
$$
例
数式
$$
f: (\mathbb{R} \times \mathbb{R}) \to \mathbb{R}, \quad f(x, y) := x - y \\
y := 2 \\
g: \mathbb{R} \to \mathbb{R}, \quad g(x) := f(x, y) \\
g(1) = -1
$$
Scheme
(define func (lambda (x y) (- x y)))
(define y 2)
(define func_partial (lambda (x) (func x y)))
(func_partial 1) ; -1
Julia
func(x, y) = x - y
y = 2
func_partial(x) = func(x, y)
func_partial(1) # -1
JavaScript
let func = (x, y) => x - y;
const y = 2;
let func_partial = x => func(x, y);
func_partial(1); // -1
C++ (C++14以上)
auto func = [](auto x, auto y) {
return x - y;
};
const auto y = 2;
auto func_partial = [](auto x) {
return func(x, y);
};
func_partial(1); // -1
Go
func f(x int, y int) int {
return x - y
}
const y = 2
func f_partial(x int) int {
return f(x, y)
}
f_partial(1) // -1
カリー化と部分適用の関係
部分適用はカリー化で表現できる。
例えば、次のような関数 $f$ を定義したとする。
$$
f: A \times B \times C \to Z, \quad (a, b, c) \mapsto z
$$
これに対して $f$ の $A$ についてのカリー化を $g$ とすると、
$$
g: A \to (B \times C \to Z), \quad a \to g(a)
$$
であり、
$$
g(a): B \times C \to Z, \quad (b, c) \mapsto z
$$
であるから、 $g(a)$ は $f$ の $A$ についての部分適用 $f_A$ に等しい。
$$
f_A: B \times C \to Z, \quad (b, c) \mapsto z \\
g(a) = f_A
$$
逆にこのカリー化 $g$ を部分適用 $f_A$ を使って定義することもできて、
$$
g: A \to {f_A}, \quad a \mapsto f_A
$$
と定義できる。つまりカリー化は部分適用を求める関数である。その意味でカリー化は部分適用よりも抽象的な概念である。
カリー化と部分適用という言葉
上の定義ではカリー化 $g$ を関数として扱ったが、カリー化 $g$ を求める手続きをカリー化と呼んだり、カリー化 $g$ を(広義の)部分適用として呼んだりする場合もある。しかし、カリー化と部分適用という言葉が使い分けられている場合、上の定義のような理解をしておくとすっきりする。
カリー化と部分適用のメリット
部分適用のメリット
次のような関数 $f$ を考えよう。
$$
f: X_1 \times \cdots \times X_n \times Y_1 \times \cdots \times Y_m \to Z \\
f(x_1, \cdots, x_n, y_1, \cdots, y_m) := z
$$
簡略化のために引数の定義域を$\mathcal{X} := X_1 \times \cdots \times X_n, \mathcal{Y} = Y_1 \times \cdots \times Y_m$、引数を$\mathbf{x} := (x_1, \cdots, x_n), \mathbf{y} = (y_1, \cdots, y_m)$として、$f(\mathbf{x}, \mathbf{y}) := f(x_1, \cdots, x_n, y_1, \cdots, y_m)$とする。
$$
f: \mathcal{X} \times \mathcal{Y} \to Z \\
f(\mathbf{x}, \mathbf{y}) := z
$$
今、関数 $f$ の引数 $\mathbf{x}$ は定数であって、変数部分は $\mathbf{y}$ だけとみなせるとしよう。その場合、いちいち
$f(\mathbf{x}, \mathbf{y})$ と書くことは、意識する必要のない $\mathbf{x}$ を何度も書くことになってしまう。これは冗長であるし、表現を複雑にする。そのようなとき、新しく $\mathbf{x}$ を定数に制限した関数 $f_\mathbf{x}$ を次のように定義して、使用すれば簡潔さを維持できる。
$$
f_\mathbf{x}: \mathcal{Y} \to Z \\
f_\mathbf{x}(\mathbf{y}) := f(\mathbf{x}, \mathbf{y}) = z
$$
これが部分適用のメリットである。
カリー化のメリット
部分適用は関数$f$の意識する必要のない引数$\mathbf{x}$を切り離すところに価値があった。一方で、$\mathbf{x}$の値について固定してしまうので後で変更できなくなってしまう。そもそも部分適用したくなる場合というのは$\mathbf{x}$と$\mathbf{y}$を切り離した方が見通しが良くなる場合に相当する。このような場合に$f_\mathbf{x}(\mathbf{y})$を$\mathbf{x}$の関数とみるのがカリー化である。
$$
f_{\text{currying}\ \mathcal{X}}: \mathcal{X} \to (\mathcal{Y} \to Z) \\
f_{\text{currying}\ \mathcal{X}}(\mathbf{x}) = f_\mathbf{x}
$$
カリー化では、部分適用のように$\mathbf{x}$を隠し、$\mathbf{y}$を変数として見る場合に加えて、$\mathbf{y}$を隠し、$\mathbf{x}$を変数として見ることができるようになっている。これがカリー化のメリットである。