Di recente ho frequentato un corso online sui linguaggi di programmazione in cui, tra gli altri concetti, sono state presentate le chiusure. Scrivo due esempi ispirati a questo corso per fornire un contesto prima di porre la mia domanda.
Il primo esempio è una funzione SML che produce un elenco dei numeri da 1 a x, dove x è il parametro della funzione:
fun countup_from1 (x: int) =
let
fun count (from: int) =
if from = x
then from :: []
else from :: count (from + 1)
in
count 1
end
Nel REPL SML:
val countup_from1 = fn : int -> int list
- countup_from1 5;
val it = [1,2,3,4,5] : int list
La countup_from1
funzione utilizza la chiusura dell'helper count
che acquisisce e utilizza la variabile x
dal suo contesto.
Nel secondo esempio, quando invoco una funzione create_multiplier t
, torno indietro una funzione (in realtà, una chiusura) che moltiplica il suo argomento per t:
fun create_multiplier t = fn x => x * t
Nel REPL SML:
- 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
Quindi la variabile m
è legata alla chiusura restituita dalla chiamata di funzione e ora posso usarla a piacimento.
Ora, affinché la chiusura funzioni correttamente per tutta la sua durata, dobbiamo estendere la durata della variabile acquisita t
(nell'esempio è un numero intero ma potrebbe essere un valore di qualsiasi tipo). Per quanto ne so, in SML questo è reso possibile dalla garbage collection: la chiusura mantiene un riferimento al valore acquisito che viene successivamente eliminato dal garbage collector quando la chiusura viene distrutta.
La mia domanda: in generale, la raccolta dei rifiuti è l'unico meccanismo possibile per garantire che le chiusure siano sicure (richiamabili per tutta la loro vita)?
O quali altri meccanismi potrebbero garantire la validità delle chiusure senza garbage collection: copiare i valori acquisiti e archiviarli all'interno della chiusura? Limitare la durata della chiusura stessa in modo che non possa essere invocata dopo che le sue variabili acquisite sono scadute?
Quali sono gli approcci più popolari?
MODIFICARE
Non credo che l'esempio sopra possa essere spiegato / implementato copiando le variabili catturate nella chiusura. In generale, le variabili acquisite possono essere di qualsiasi tipo, ad esempio possono essere associate a un elenco molto grande (immutabile). Pertanto, nell'implementazione sarebbe molto inefficiente copiare questi valori.
Per completezza, ecco un altro esempio usando riferimenti (ed effetti collaterali):
(* 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 ();
Nel REPL SML:
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
Pertanto, le variabili possono anche essere acquisite per riferimento e sono ancora attive dopo il completamento della chiamata di funzione che le ha create ( create_counter ()
).