無環境関数とクロージャ関数

無環境 (envless) という単語は一般的な単語ではなく、 私は "環境の無い (environmentless)" の略記として使いますが、 その意味をあなたは問題なく想像できるでしょう。

無環境関数は、この関数の呼び出しを実行するオブジェクトコードが配置されているコードセグメントを指し示すポインタによって表現されます。 C言語において、全ての関数は無環境です。 クロージャ関数もまたポインタで表現されますが、 そのポインタはヒープ中の(実行時に)割り当てられたタプルがある場所を指し示しています。 通常、このタプルの1つ目の要素は無環境関数を表わすポインタで、残りの要素は束縛群を表わしています。 そのようなタプルはしばしばクロージャ関数もしくは単純にクロージャと呼ばれ、 環境とペアになった無環境関数であると考えることができます。 クロージャ関数の環境が空であっても問題ありませんが、 これは無環境関数のクロージャ関数と同じではありません。 ML や Haskell のような関数型言語の全ての関数はクロージャ関数です。

次の例では、型 (int) -> int が割り当てられた関数 sum は1から与えられた自然数までの全ての整数を合計します:

fun sum (n: int): int = let fun loop ( i: int, res: int ) :<cloref1> int = if i <= n then loop (i+1, res+i) else res // end of [loop] in loop (1(*i*), 0(*res*)) end // end of [sum]

内側の関数 loop は特別な構文 :<cloref1> で示されるクロージャ関数です。 また loop に割り当てられた型は (int, int) -<cloref1> int です。 それゆえ無環境関数とクロージャ関数は型レベルで見分けることができます。

もし構文 :<cloref1> を単独なコロン記号 : に置き換えたとしてもコードは型検査を通りますが、loop をC言語のトップレベル関数にコンパイルできないことを示す警告もしくはエラーを引き起こす可能性があります。 この警告/エラーの理由は、トップレベルでもなく loop 関数自身の引数でもない値 nloop の本体に含まれていることにあります。 n を元の関数への追加の引数として loop を無環境関数にするのは簡単です:

fun sum (n: int): int = let fun loop ( n:int, i: int, res: int ) : int = if i <= n then loop (n, i+1, res+i) else res // end of [loop] in loop (n, 1(*i*), 0(*res*)) end // end of [sum]

実際のコンパイルの中では、 sumloop の最初の実装はおおよそ2番目の実装に変換され、 実行時に実際のクロージャが作られるわけではありません

関数が直接適用されない時でも、しばしばクロージャを作る必要があります。 例えば、関数呼び出しの返値もまた関数であるかもしれません。 次のコードで定義された関数 addx は与えられた整数 x に適用した別の関数を返します。 そしてこの返される関数はクロージャ関数で、 常に自分の引数に整数 x を加算します:

// fun addx (x: int): int -<cloref1> int = lam y => x + y // val plus1 = addx (1) // [plus1] is of the type int -<cloref1> int val plus2 = addx (2) // [plus2] is of the type int -<cloref1> int //

plus1(0)plus2(0) がそれぞれ 12 を返すのは自明でしょう。 plus1 の名前が与えられたクロージャ関数は無環境関数と x1 に束縛した環境から構成されています。 envenv.x が環境とその環境で x に束縛された値を参照する時、 この無環境関数は仮構文を使って本質的に lam (env, y) => env.x + y と表現できます。 plus1(0) を評価すると、 はじめに envy をそれぞれ plus1 内の環境と引数 0 に束縛します。 それから、 env.x + y である plus1 内の無環境関数の本体の評価を開始します。 この評価によって期待通り値 1 が生成されることは明確です。

クロージャはしばしば高階関数と呼ばれる関数への引数として渡されることがあります。 クロージャがデータ構造に埋め込まれることも一般的です。