Нещодавно я відвідував онлайн-курс з мов програмування, на якому, серед інших концепцій, були представлені закриття. Я записую два приклади, натхненні цим курсом, щоб дати деякий контекст, перш ніж ставити своє запитання.
Перший приклад - функція 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 ()
).