J'ai récemment suivi un cours en ligne sur les langages de programmation dans lequel, entre autres concepts, des fermetures ont été présentées. J'écris deux exemples inspirés de ce cours pour donner un peu de contexte avant de poser ma question.
Le premier exemple est une fonction SML qui produit une liste des nombres de 1 à x, où x est le paramètre de la fonction:
fun countup_from1 (x: int) =
let
fun count (from: int) =
if from = x
then from :: []
else from :: count (from + 1)
in
count 1
end
Dans le SML REPL:
val countup_from1 = fn : int -> int list
- countup_from1 5;
val it = [1,2,3,4,5] : int list
La countup_from1
fonction utilise la fermeture d'aide count
qui capture et utilise la variable à x
partir de son contexte.
Dans le deuxième exemple, lorsque j'appelle une fonction create_multiplier t
, je récupère une fonction (en fait, une fermeture) qui multiplie son argument par t:
fun create_multiplier t = fn x => x * t
Dans le 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
Donc, la variable m
est liée à la fermeture retournée par l'appel de fonction et maintenant je peux l'utiliser à volonté.
Maintenant, pour que la fermeture fonctionne correctement tout au long de sa durée de vie, nous devons prolonger la durée de vie de la variable capturée t
(dans l'exemple, il s'agit d'un entier mais il peut s'agir d'une valeur de n'importe quel type). Pour autant que je sache, en SML, cela est rendu possible par la collecte des ordures: la fermeture conserve une référence à la valeur capturée qui est ensuite éliminée par le garbage collector lorsque la fermeture est détruite.
Ma question: en général, la collecte des ordures est-elle le seul mécanisme possible pour garantir la sécurité des fermetures (appelables pendant toute leur durée de vie)?
Ou quels sont les autres mécanismes qui pourraient garantir la validité des fermetures sans ramassage des ordures: copier les valeurs capturées et les stocker dans la fermeture? Restreindre la durée de vie de la fermeture elle-même afin qu'elle ne puisse pas être invoquée après l'expiration de ses variables capturées?
Quelles sont les approches les plus populaires?
ÉDITER
Je ne pense pas que l'exemple ci-dessus puisse être expliqué / implémenté en copiant la ou les variables capturées dans la fermeture. En général, les variables capturées peuvent être de tout type, par exemple, elles peuvent être liées à une très grande liste (immuable). Ainsi, dans l'implémentation, il serait très inefficace de copier ces valeurs.
Par souci d'exhaustivité, voici un autre exemple utilisant des références (et des effets secondaires):
(* 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 ();
Dans le 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
Ainsi, les variables peuvent également être capturées par référence et sont toujours actives une fois l'appel de fonction qui les a créées ( create_counter ()
) terminé.