Нещодавно я відвідував онлайн-курс з мов програмування, на якому, серед інших концепцій, були представлені закриття. Я записую два приклади, натхненні цим курсом, щоб дати деякий контекст, перш ніж ставити своє запитання.
Перший приклад - функція SML, яка створює список чисел від 1 до x, де x - параметр функції:
fun countup_from1 (x: int) =
let
fun count (from: int) =
if from = x
then from :: []
else from :: count (from + 1)
in
count 1
end
У відповіді про SML:
val countup_from1 = fn : int -> int list
- countup_from1 5;
val it = [1,2,3,4,5] : int list
countup_from1Функція використовує замикання помічника , countякий збирає і використовує змінну xз контексту.
У другому прикладі, коли я викликаю функцію create_multiplier t, я повертаю функцію (власне, закриття), яка помножує її аргумент на t:
fun create_multiplier t = fn x => x * t
У відповіді про SML:
- 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
Таким чином, змінна mприв’язана до закриття, поверненого викликом функції, і тепер я можу використовувати її за бажанням.
Тепер, щоб закриття працювало належним чином протягом усього його життя, нам потрібно продовжити термін служби захопленої змінної t(у прикладі це ціле число, але це може бути значення будь-якого типу). Наскільки я знаю, в SML це стає можливим шляхом вивезення сміття: закриття зберігає посилання на захоплене значення, яке згодом утилізується сміттєзбірником при знищенні закриття.
Моє запитання: загалом, чи єдиний можливий механізм вивезення сміття забезпечує безпечне закриття (дзвонить протягом усього життя)?
Або які інші механізми можуть забезпечити обґрунтованість закриття без вивезення сміття: Скопіюйте захоплені значення та зберігайте їх всередині закриття? Обмежте термін служби самого закриття, щоб його не можна було викликати після закінчення терміну дії захоплених змінних?
Які найпопулярніші підходи?
EDIT
Я не думаю, що наведений вище приклад не можна пояснити / реалізувати, скопіювавши захоплену змінну (и) у закриття. Загалом, захоплені змінні можуть бути будь-якого типу, наприклад, вони можуть бути прив'язані до дуже великого (незмінного) списку. Отже, при реалізації було б дуже неефективно копіювати ці значення.
Для повноти, ось ще один приклад використання посилань (та побічних ефектів):
(* 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 ();
У відповіді про SML:
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
Таким чином, змінні також можуть бути зафіксовані за посиланням і залишаються живими після завершення виклику функції, який створив їх ( create_counter ()).