Hace poco asistí a un curso en línea sobre lenguajes de programación en el que, entre otros conceptos, se presentaron los cierres. Escribo dos ejemplos inspirados en este curso para dar un poco de contexto antes de hacer mi pregunta.
El primer ejemplo es una función SML que produce una lista de los números de 1 a x, donde x es el parámetro de la función:
fun countup_from1 (x: int) =
let
fun count (from: int) =
if from = x
then from :: []
else from :: count (from + 1)
in
count 1
end
En el SML REPL:
val countup_from1 = fn : int -> int list
- countup_from1 5;
val it = [1,2,3,4,5] : int list
La función countup_from1
usa el cierre de ayuda count
que captura y usa la variable x
de su contexto.
En el segundo ejemplo, cuando invoco una función create_multiplier t
, recupero una función (en realidad, un cierre) que multiplica su argumento por t:
fun create_multiplier t = fn x => x * t
En el 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
Entonces, la variable m
está vinculada al cierre devuelto por la llamada a la función y ahora puedo usarlo a voluntad.
Ahora, para que el cierre funcione correctamente durante toda su vida útil, necesitamos extender la vida útil de la variable capturada t
(en el ejemplo, es un número entero pero podría ser un valor de cualquier tipo). Por lo que sé, en SML esto es posible gracias a la recolección de basura: el cierre mantiene una referencia al valor capturado, que luego es eliminado por el recolector de basura cuando
El cierre se destruye.
Mi pregunta: en general, es la recolección de basura el único mecanismo posible para ¿Asegurarse de que los cierres sean seguros (que se puedan reclamar durante toda su vida útil)?
O, ¿cuáles son otros mecanismos que podrían garantizar la validez de los cierres sin recolección de basura: copiar los valores capturados y almacenarlos dentro del cierre? ¿Restrinja la vida útil del cierre para que no se pueda invocar después de que hayan expirado las variables capturadas?
¿Cuáles son los enfoques más populares?
EDIT
No creo que el ejemplo anterior se pueda explicar / implementar copiando las variables capturadas en el cierre. En general, las variables capturadas pueden ser de cualquier tipo, por ejemplo pueden estar vinculados a una lista muy grande (inmutable). Por lo tanto, en la implementación sería muy ineficiente copiar estos valores.
En aras de la integridad, aquí hay otro ejemplo que usa referencias (y efectos secundarios):
(* 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 ();
En el 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
Por lo tanto, las variables también se pueden capturar por referencia y siguen vivas después de
la llamada a la función que los creó ( create_counter ()
) se ha completado.