クロージャ関数はボックス化レコードで、無環境関数へのポインタと、その無環境関数内で使われる名前から値をマップする束縛を含んでいます。 実際には、高階関数の関数引数はしばしば (無環境関数の代わりに) クロージャ関数です。 例えば、次の高階関数 list_map_cloref はその第2引数としてクロージャ関数を取ります:
fun{ a:t@ype}{b:t@ype } list_map_cloref{n:int} (xs: list (a, n), f: (a) -<cloref> b): list_vt (b, n)
クロージャ関数は線形にも非線形にも扱うことができ、線形クロージャ関数は安全な作法で明示的に解放することができます。 キーワード -<cloref> は非線形クロージャ関数の型を作るのに使われます。 list_map_cloref の変形として、次の高階関数 list_map_cloptr はその第2引数として線形クロージャ関数を取ります:
fun{ a:t@ype}{b:t@ype } list_map_cloptr{n:int} (xs: list (a, n), f: !(a) -<cloptr> b): list_vt (b, n)
list_map_cloptr の典型的な使用例は次のようになります:
fun foo{n:int} ( x0: int, xs: list (int, n) ) : list_vt (int, n) = res where { // val f = lam (x) =<cloptr> x0 + x val res = list_map_cloptr (xs, f) val () = cloptr_free ($UNSAFE.cast{cloptr(void)}(f)) // } (* end of [foo] *)
また、カリー化と線形クロージャ関数の間にはいくつか興味深い相互作用があります。 関数型プログラミングではカリー化は、複数の引数を同時に取る関数を、それらの引数を連続して取るように変更することを意味します。 例えば、次のコードの関数 acker2 は関数 acker のカリー化されたバージョンです。 この関数は有名な (原始再帰でない再帰である) アッカーマン関数を実装しています:
fun acker(m:int, n:int): int = ( case+ (m, n) of | (0, _) => n + 1 | (m, 0) => acker (m-1, 1) | (_, _) => acker (m-1, acker (m, n-1)) ) (* end of [acker] *) fun acker2 (m:int) (n:int): int = acker (m, n)
また acker のカリー化されたバージョンを次のように定義することもできます:
acker3(3)(4) の評価は acker2(3)(4) と同じ結果になりますが、ATS (ATS/Postiats) コンパイラは acker3(3) の評価によって生成された線形クロージャ関数を acker3(3)(4) の評価が完了した後に自動解放するコードを挿入します。ATS1 では、GC が使えない場面での高階関数利用したプログラミングをサポートするために、線形クロージャ関数は重要な役目を演じます。 ATS2 は高度なテンプレートをサポートするので、ATS1 で線形クロージャ関数が演じた役目は非常に弱まりました。 けれども、もし GC が使えないもしくは望まれない場面でクロージャ関数をデータ構造に保存する必要があるのであれば、線形クロージャ関数の利用がメモリリークの危険を避ける解決策になるかもしれません。
この章で紹介したコードの全体は オンライン から入手できます。