Recentemente ho partecipato a un corso online sui linguaggi di programmazione in cui, tra gli altri concetti, sono state presentate le chiusure. Annoto due esempi ispirati a questo corso per dare qualche 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
In SML REPL:
val countup_from1 = fn : int -> int list
- countup_from1 5;
val it = [1,2,3,4,5] : int list
La funzione countup_from1
utilizza la chiusura helper count
che cattura e utilizza la variabile x
dal suo contesto.
Nel secondo esempio, quando invoco una funzione create_multiplier t
, ritorno a una funzione (in realtà, una chiusura) che moltiplica il suo argomento per t:
fun create_multiplier t = fn x => x * t
In 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
Quindi la variabile m
è legata alla chiusura restituita dalla chiamata alla funzione e ora posso usarla a volontà.
Ora, affinché la chiusura funzioni correttamente per tutta la sua durata, dobbiamo estendere la durata della variabile catturata t
(nell'esempio è un numero intero ma potrebbe essere un valore di qualsiasi tipo). Per quanto ne so, in SML ciò è reso possibile dalla garbage collection: la chiusura mantiene un riferimento al valore catturato che viene successivamente smaltito dal garbage collector quando
la chiusura è distrutta.
La mia domanda: in generale, la garbage collection è l'unico meccanismo possibile per assicurarsi che le chiusure siano sicure (richiamabili durante tutta la loro vita)?
O quali sono altri meccanismi che potrebbero garantire la validità delle chiusure senza garbage collection: copiare i valori catturati e memorizzarli all'interno della chiusura? Limita la durata della chiusura stessa in modo che non possa essere invocato dopo che le sue variabili catturate sono scadute?
Quali sono gli approcci più popolari?
Modifica
Non penso 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 es. possono essere associati a una lista molto grande (immutabile). Quindi, nell'implementazione sarebbe molto inefficiente copiarli valori.
Per completezza, ecco un altro esempio che usa i riferimenti (e 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 ();
In 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
Quindi, le variabili possono anche essere catturate per riferimento e sono ancora vivi dopo
la chiamata alla funzione che li ha creati ( create_counter ()
) è stata completata.