カリー化された関数
qiitaの週間ランキング?の上位に、カリー化された関数の話が出ていたけど、分かりにくいなぁと思ったので、自分なりに記事を書いてみる。
ちなみにカリー化された関数は英語訳するとcurried functionになる。
Standard ML of New Jersey という言語がある。その言語は、メジャーな言語と比べるとなんか癖が強いと思うけど、言語として、いろんな特徴が実装されているように思える。その 1 つにカリー化がある。
smlnjをインストールすると、インタープリタが実行できるようになる。
% sml Standard ML of New Jersey v110.85 [built: Sat Dec 22 16:51:02 2018] - val a: int = 2 + 3; val a = 5 : int
例えばこんな風に。
関数の定義は、
- fun add a b: int = a + b;
こういう構文になる。
呼び出しはこう。
- add 2 4; val it = 6 : int
ところで、smlnjは、静的な型付言語で、関数を定義すると、型が出力される。
- fun increment (a:int) : int = a+1; val increment = fn : int -> int
この場合、incrementという変数に、ラムダ式を代入したような、表記が出力されており、int型の引数を渡すとint型の値が帰ってくるようなのが書かれているのがわかると思う。
引数が増えると、どうなるのか予想して見て欲しい。 最初、僕は
- fun add a b: int = a + b; val add = fn : int, int -> int
こうかと思ってた。でも、違った。
- fun add a b: int = a + b; val add = fn : int -> int -> int
実際はこうだった。 int -> int -> int は、わかりやすく書くと、int -> (int -> int)のことで、これは、実際に、1 つの引数でも呼べることを意味している。
- val add2 = add 3; val add2 = fn : int -> int
これを宣言しておくと、
- add2 5; val it = 8 : int - add2 19; val it = 22 : int
こんな風な呼び出しができるようになる。つまり、部分適用ができるようになる。
もちろん、僕が最初に思ったような書き方もできる。
- fun add (a,b) = a+b; val add = fn : int * int -> int
タプル(tuple)を使用すると最初に思ったような関数を宣言することができる。
こんな風に、部分的な動作をする関数を返すことができる関数をキャリー化された関数というらしい。
正確には、wikiとかをみると、写像とかの詳しい話が出ていて、部分適用が行いやすくなるという程度にとどまっている。
wikiを読むと、カリー化と部分適用を混同するなよって書いてある。
部分適用は、あくまで、add(3)でも呼べるようにすることで、カリー化は、add(x)とした時の動作を規定することって書いてある(ように感じた)。