Chapter 26. 線形クロージャ関数

クロージャ関数はボックス化レコードで、無環境関数へのポインタと、その無環境関数内で使われる名前から値をマップする束縛を含んでいます。 実際には、高階関数の関数引数はしばしば (無環境関数の代わりに) クロージャ関数です。 例えば、次の高階関数 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)

容易に推測できることですが、キーワード -<cloptr> は線形クロージャ関数の型を作るのに使われます。 記号 ! は、list_map_cloptr が返った後でも第2引数がまだ有効であることを示すことに注意してください。

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] *)

線形クロージャは、最初に関数 foo の本体で生成されて、使用された後に明示的に解放されていることに注意してください。 関数 cloptr_free は次のようなインターフェイスです:

fun cloptr_free {a:t0p} (pclo: cloptr (a)): void

このとき cloptr は抽象的です。 キャスト $UNSAFE.cast{cloptr(void)}(f) は確かにより安全なものに変更できますが、プログラミングがより面倒になるでしょう。

また、カリー化と線形クロージャ関数の間にはいくつか興味深い相互作用があります。 関数型プログラミングではカリー化は、複数の引数を同時に取る関数を、それらの引数を連続して取るように変更することを意味します。 例えば、次のコードの関数 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)

acker2 に2つの整数 3 と 4 を適用したとしましょう: acker2(3)(4); 適用 acker2(3) は (非線形な) クロージャ関数に評価されます; このクロージャ関数に 4 を適用すると acker(3,4) に評価され、さらに評価されて整数 125 になります。 acker2(3) を評価して生成されたクロージャ関数はヒープに確保された値で、acker2(3)(4) の評価が完了した後ではもはや利用できないことに注意してください。 そのような値のメモリは GC によって回収される必要があります。

また acker のカリー化されたバージョンを次のように定義することもできます:

fun acker3 (m:int) = lam (n:int): int =<cloptr1> acker (m, n)

acker3(3)(4) の評価は acker2(3)(4) と同じ結果になりますが、ATS (ATS/Postiats) コンパイラは acker3(3) の評価によって生成された線形クロージャ関数を acker3(3)(4) の評価が完了した後に自動解放するコードを挿入します。

ATS1 では、GC が使えない場面での高階関数利用したプログラミングをサポートするために、線形クロージャ関数は重要な役目を演じます。 ATS2 は高度なテンプレートをサポートするので、ATS1 で線形クロージャ関数が演じた役目は非常に弱まりました。 けれども、もし GC が使えないもしくは望まれない場面でクロージャ関数をデータ構造に保存する必要があるのであれば、線形クロージャ関数の利用がメモリリークの危険を避ける解決策になるかもしれません。

この章で紹介したコードの全体は オンライン から入手できます。