14
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

カリー化と部分適用

Last updated at Posted at 2017-11-28

カリー化と部分適用という概念について簡潔に説明する。

カリー化 / 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}$を変数として見ることができるようになっている。これがカリー化のメリットである。

14
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?