ITエンジニアのブログ

IT企業でエンジニアやってる人間の日常について

プログラミング言語を作る。第11回:カリー化と部分適用

言語仕様の詳細を練っていきます。関数型言語では、カリー化や関数の部分適用が可能な言語が多くなっています。カリー化によって、関数の引数を一つずつ与えられるようになり、関数を部分的に適用することができるようになります。

例えば、

add x y = x + y

という関数があるとします。通常は、この関数を用いるとき、引数を二つ与えて

add 2 3
=> 5

とするでしょう。これがカリー化された場合、この関数は

add = \x -> \y -> x + y

というように分解されます。これを行うことで。 add という関数に引数を一つだけ与えて新たな関数を作ることが可能です。部分適用です。

add1 = add 1
add1 2
=> 3
add2 = add 2
add2 10
=> 12

自作プログラミング言語にも、この機能を搭載してみようと思います。

関数の適用について値がいつ評価されるかについても、気になったので確認しておきました。例えば、 OCaml において let f x y = body は let f = fun x -> fun y -> body と同じ意味になります。 fun の直前までは評価されるので、例えば次のような表記の違いによって評価が違ってきます。

# let f1 x = Random.int 10 + x;;
val f1 : int -> int = <fun>
# List.map f1 [1;2;3;4;5];;
- : int list = [6; 5; 5; 12; 9]
# let f2 = let i = Random.int 10 in fun x -> i + x;;
val f2 : int -> int = <fun>
# List.map f2 [1;2;3;4;5];;
- : int list = [3; 4; 5; 6; 7]

f1 は x が与えられてから乱数の取得が行われるため、map で足している数が毎回値が違っていますが、 f2 は x が与えられる前に乱数が取得されているので、足す数が固定されています。

次のような式をコンパイルするときに、以上のことは注意しなければなりません。

def f1 x :: Int -> Int {
  var i = rand ();
  return i + x;
}

def f2 :: Int -> Int {
  var i = rand ();
  return \x -> x + i;
}