Ich habe kürzlich einen Online-Kurs über Programmiersprachen besucht, in dem unter anderem Abschlussarbeiten vorgestellt wurden. Ich schreibe zwei Beispiele auf, die von diesem Kurs inspiriert wurden, um einen Kontext zu geben, bevor ich meine Frage stelle.
Das erste Beispiel ist eine SML-Funktion, die eine Liste der Zahlen von 1 bis x erzeugt, wobei x der Parameter der Funktion ist:
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 der SML REPL:
val countup_from1 = fn : int -> int list
- countup_from1 5;
val it = [1,2,3,4,5] : int list
Die countup_from1
Funktion verwendet den Helferabschluss count
, der die Variable x
aus ihrem Kontext erfasst und verwendet .
Wenn ich im zweiten Beispiel eine Funktion aufrufe create_multiplier t
, erhalte ich eine Funktion (eigentlich einen Abschluss) zurück, die ihr Argument mit t multipliziert:
fun create_multiplier t = fn x => x * t
In der 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
Die Variable m
ist also an den vom Funktionsaufruf zurückgegebenen Abschluss gebunden, und jetzt kann ich sie nach Belieben verwenden.
Damit der Abschluss während seiner gesamten Lebensdauer ordnungsgemäß funktioniert, müssen wir die Lebensdauer der erfassten Variablen verlängern t
(im Beispiel ist es eine Ganzzahl, aber es kann sich um einen beliebigen Wert handeln). Soweit ich weiß, wird dies in SML durch die Garbage Collection ermöglicht: Der Verschluss behält einen Verweis auf den erfassten Wert bei, der später vom Garbage Collector entsorgt wird, wenn der Verschluss zerstört wird.
Meine Frage: Ist die Speicherbereinigung im Allgemeinen der einzig mögliche Mechanismus, um sicherzustellen, dass die Schließungen sicher sind (abrufbar während ihrer gesamten Lebensdauer)?
Oder welche anderen Mechanismen könnten die Gültigkeit von Verschlüssen ohne Garbage Collection sicherstellen: Kopieren Sie die erfassten Werte und speichern Sie sie innerhalb des Verschlusses? Die Lebensdauer des Verschlusses selbst einschränken, sodass er nach Ablauf der erfassten Variablen nicht mehr aufgerufen werden kann?
Was sind die beliebtesten Ansätze?
BEARBEITEN
Ich denke nicht, dass das obige Beispiel erklärt / implementiert werden kann, indem die erfassten Variablen in den Abschluss kopiert werden. Im Allgemeinen können die erfassten Variablen von einem beliebigen Typ sein, z. B. können sie an eine sehr große (unveränderliche) Liste gebunden sein. Daher wäre es in der Implementierung sehr ineffizient, diese Werte zu kopieren.
Der Vollständigkeit halber ist hier ein weiteres Beispiel unter Verwendung von Referenzen (und Nebenwirkungen):
(* 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 der 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
Variablen können also auch als Referenz erfasst werden und sind nach Abschluss des Funktionsaufrufs, der sie erstellt hat ( create_counter ()
), noch aktiv.