私は最近、プログラミング言語のオンラインコースに参加しました。このコースでは、概念の中でも特にクロージャが紹介されました。質問をする前に、このコースに触発された2つの例を書き留めて、コンテキストを説明します。
最初の例は、1からxまでの数字のリストを生成するSML関数です。xは関数のパラメーターです。
fun countup_from1 (x: int) =
let
fun count (from: int) =
if from = x
then from :: []
else from :: count (from + 1)
in
count 1
end
SML REPLで:
val countup_from1 = fn : int -> int list
- countup_from1 5;
val it = [1,2,3,4,5] : int list
このcountup_from1
関数は、コンテキストからcount
変数をキャプチャして使用するヘルパークロージャーを使用x
します。
2番目の例では、functionを呼び出すと、create_multiplier t
引数にtを乗算する関数(実際にはクロージャー)が返されます。
fun create_multiplier t = fn x => x * t
SML REPLで:
- fun create_multiplier t = fn x => x * t;
val create_multiplier = fn : int -> int -> int
- val m = create_multiplier 10;
val m = fn : int -> int
- m 4;
val it = 40 : int
- m 2;
val it = 20 : int
したがって、変数m
は関数呼び出しによって返されたクロージャーにバインドされ、自由に使用できるようになりました。
さて、クロージャーがそのライフタイム全体で適切に機能するためには、キャプチャされた変数のライフタイムを延長する必要がありますt
(この例では整数ですが、任意の型の値になる可能性があります)。私の知る限り、SMLではこれはガベージコレクションによって可能になります。クロージャはキャプチャされた値への参照を保持します。キャプチャされた値は、クロージャが破棄されたときにガベージコレクタによって破棄されます。
私の質問:一般に、ガベージコレクションは、クロージャが安全であることを保証する唯一の可能なメカニズムですか(そのライフタイム全体で呼び出し可能)?
または、ガベージコレクションなしでクロージャの有効性を保証できる他のメカニズムは何ですか?キャプチャされた値をコピーしてクロージャ内に保存しますか?キャプチャーされた変数の有効期限が切れた後に呼び出せないように、クロージャー自体のライフタイムを制限しますか?
最も一般的なアプローチは何ですか?
編集
キャプチャされた変数をクロージャーにコピーすることで、上記の例を説明/実装できるとは思いません。一般に、キャプチャされた変数は任意のタイプにできます。たとえば、非常に大きな(不変の)リストにバインドできます。したがって、実装では、これらの値をコピーすることは非常に非効率的です。
完全を期すために、参照(および副作用)を使用した別の例を次に示します。
(* Returns a closure containing a counter that is initialized
to 0 and is incremented by 1 each time the closure is invoked. *)
fun create_counter () =
let
(* Create a reference to an integer: allocate the integer
and let the variable c point to it. *)
val c = ref 0
in
fn () => (c := !c + 1; !c)
end
(* Create a closure that contains c and increments the value
referenced by it it each time it is called. *)
val m = create_counter ();
SML REPLで:
val create_counter = fn : unit -> unit -> int
val m = fn : unit -> int
- m ();
val it = 1 : int
- m ();
val it = 2 : int
- m ();
val it = 3 : int
そのため、変数は参照によってキャプチャすることもでき、変数を作成した関数呼び出し(create_counter ()
)が完了した後も引き続き有効です。