Niedawno uczestniczyłem w kursie internetowym na temat języków programowania, w którym zaprezentowano między innymi zamknięcia. Zapisuję dwa przykłady zainspirowane tym kursem, aby podać kontekst, zanim zadam pytanie.
Pierwszym przykładem jest funkcja SML, która tworzy listę liczb od 1 do x, gdzie x jest parametrem funkcji:
fun countup_from1 (x: int) =
let
fun count (from: int) =
if from = x
then from :: []
else from :: count (from + 1)
in
count 1
end
W SML REPL:
val countup_from1 = fn : int -> int list
- countup_from1 5;
val it = [1,2,3,4,5] : int list
countup_from1
Funkcja wykorzystuje zamknięcie pomocnika count
, który przechwytuje i używa zmiennej x
od jej kontekstu.
W drugim przykładzie, kiedy wywołuję funkcję create_multiplier t
, zwracam funkcję (a właściwie zamknięcie), która zwielokrotnia jej argument przez t:
fun create_multiplier t = fn x => x * t
W 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
Zmienna m
jest więc powiązana z zamknięciem zwróconym przez wywołanie funkcji i teraz mogę z niej korzystać do woli.
Teraz, aby zamknięcie działało poprawnie przez cały okres jego istnienia, musimy przedłużyć czas życia przechwyconej zmiennej t
(w tym przykładzie jest ona liczbą całkowitą, ale może być wartością dowolnego typu). O ile mi wiadomo, w SML jest to możliwe dzięki wyrzucaniu elementów bezużytecznych: zamknięcie zachowuje odniesienie do przechwyconej wartości, która jest później usuwana przez moduł wyrzucający elementy bezużyteczne, gdy zamknięcie zostanie zniszczone.
Moje pytanie: czy ogólnie rzecz biorąc, czy usuwanie śmieci jest jedynym możliwym mechanizmem zapewniającym bezpieczeństwo zamknięć (możliwość wywołania przez cały okres ich użytkowania)?
Lub jakie są inne mechanizmy, które mogą zapewnić ważność zamknięć bez wyrzucania elementów bezużytecznych: Skopiuj przechwycone wartości i zapisz je w zamknięciu? Ogranicz czas życia samego zamknięcia, aby nie można go było wywoływać po wygaśnięciu przechwyconych zmiennych?
Jakie są najbardziej popularne podejścia?
EDYTOWAĆ
Nie sądzę, że powyższy przykład można wyjaśnić / wdrożyć, kopiując przechwycone zmienne (zmienne) do zamknięcia. Zasadniczo, przechwycone zmienne mogą być dowolnego typu, np. Mogą być powiązane z bardzo dużą (niezmienną) listą. Tak więc we wdrożeniu kopiowanie tych wartości byłoby bardzo nieefektywne.
Dla kompletności, oto kolejny przykład wykorzystujący odniesienia (i skutki uboczne):
(* 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 ();
W 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
Tak więc zmienne mogą być również przechwytywane przez odniesienie i nadal są żywe po zakończeniu wywołania funkcji, która je utworzyła ( create_counter ()
).