Recentemente, participei de um curso on-line sobre linguagens de programação, no qual, entre outros conceitos, foram apresentados encerramentos. Escrevo dois exemplos inspirados neste curso para dar algum contexto antes de fazer minha pergunta.
O primeiro exemplo é uma função SML que produz uma lista dos números de 1 a x, em que x é o parâmetro da função:
fun countup_from1 (x: int) =
let
fun count (from: int) =
if from = x
then from :: []
else from :: count (from + 1)
in
count 1
end
No SML REPL:
val countup_from1 = fn : int -> int list
- countup_from1 5;
val it = [1,2,3,4,5] : int list
A countup_from1
função usa o fechamento auxiliar count
que captura e usa a variável x
de seu contexto.
No segundo exemplo, quando invoco uma função create_multiplier t
, recebo de volta uma função (na verdade, um fechamento) que multiplica seu argumento por t:
fun create_multiplier t = fn x => x * t
No 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
Portanto, a variável m
está vinculada ao fechamento retornado pela chamada de função e agora posso usá-la à vontade.
Agora, para que o fechamento funcione adequadamente durante toda a sua vida útil, precisamos estender a vida útil da variável capturada t
(no exemplo, é um número inteiro, mas pode ser um valor de qualquer tipo). Tanto quanto eu sei, no SML isso é possível pela coleta de lixo: o fechamento mantém uma referência ao valor capturado que é posteriormente descartado pelo coletor de lixo quando o fechamento é destruído.
Minha pergunta: em geral, a coleta de lixo é o único mecanismo possível para garantir que os fechamentos sejam seguros (exigíveis durante toda a vida útil)?
Ou quais são outros mecanismos que poderiam garantir a validade dos fechamentos sem coleta de lixo: copie os valores capturados e armazene-os dentro do fechamento? Restringir o tempo de vida do próprio fechamento para que ele não possa ser chamado depois que suas variáveis capturadas expirarem?
Quais são as abordagens mais populares?
EDITAR
Eu não acho que o exemplo acima possa ser explicado / implementado copiando as variáveis capturadas no fechamento. Em geral, as variáveis capturadas podem ser de qualquer tipo, por exemplo, podem ser vinculadas a uma lista muito grande (imutável). Portanto, na implementação, seria muito ineficiente copiar esses valores.
Por uma questão de exaustividade, aqui está outro exemplo usando referências (e efeitos colaterais):
(* 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 ();
No 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
Portanto, as variáveis também podem ser capturadas por referência e ainda estão ativas após a conclusão da chamada de função que as criou ( create_counter ()
).