Baru-baru ini saya menghadiri kursus online tentang bahasa pemrograman di mana, di antara konsep-konsep lain, penutupan disajikan. Saya menuliskan dua contoh yang terinspirasi oleh kursus ini untuk memberikan konteks sebelum mengajukan pertanyaan saya.
Contoh pertama adalah fungsi SML yang menghasilkan daftar angka dari 1 hingga x, di mana x adalah parameter fungsi:
fun countup_from1 (x: int) =
let
fun count (from: int) =
if from = x
then from :: []
else from :: count (from + 1)
in
count 1
end
Di SML REPL:
val countup_from1 = fn : int -> int list
- countup_from1 5;
val it = [1,2,3,4,5] : int list
The countup_from1
Fungsi menggunakan penutupan penolong count
yang menangkap dan menggunakan variabel x
dari konteksnya.
Dalam contoh kedua, ketika saya menjalankan fungsi create_multiplier t
, saya mendapatkan kembali fungsi (sebenarnya, penutupan) yang mengalikan argumennya dengan t:
fun create_multiplier t = fn x => x * t
Di 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
Jadi variabel m
terikat pada penutupan yang dikembalikan oleh pemanggilan fungsi dan sekarang saya bisa menggunakannya sesuka hati.
Sekarang, agar penutupan berfungsi dengan baik sepanjang masa pakainya, kita perlu memperpanjang masa hidup dari variabel yang ditangkap t
(dalam contoh itu adalah bilangan bulat tetapi bisa berupa nilai dari jenis apa pun). Sejauh yang saya tahu, di SML ini dimungkinkan oleh pengumpulan sampah: penutupan menyimpan referensi ke nilai yang ditangkap yang kemudian dibuang oleh pengumpul sampah ketika penutupan dihancurkan.
Pertanyaan saya: secara umum, apakah pengumpulan sampah adalah satu-satunya mekanisme yang mungkin untuk memastikan bahwa penutupan aman (dapat dipanggil selama masa pakainya)?
Atau apakah mekanisme lain yang dapat memastikan validitas penutupan tanpa pengumpulan sampah: Salin nilai yang ditangkap dan simpan di dalam penutupan? Batasi masa berlaku penutupan itu sendiri sehingga tidak dapat dipanggil setelah variabel yang ditangkap telah kedaluwarsa?
Apa pendekatan yang paling populer?
EDIT
Saya tidak berpikir contoh di atas dapat dijelaskan / diimplementasikan dengan menyalin variabel yang ditangkap ke penutupan. Secara umum, variabel yang ditangkap dapat dari jenis apa pun, misalnya mereka dapat terikat pada daftar yang sangat besar (tidak berubah). Jadi, dalam implementasinya akan sangat tidak efisien untuk menyalin nilai-nilai ini.
Demi kelengkapan, berikut adalah contoh lain menggunakan referensi (dan efek samping):
(* 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 ();
Di 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
Jadi, variabel juga bisa ditangkap dengan referensi dan masih hidup setelah pemanggilan fungsi yang membuatnya ( create_counter ()
) telah selesai.