Недавно я посетил онлайн-курс по языкам программирования, на котором, помимо прочего, были представлены замыкания. Я записываю два примера, вдохновленных этим курсом, чтобы дать некоторый контекст, прежде чем задавать мой вопрос.
Первый пример - это функция 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 REPL:
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 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
Таким образом, переменная m
связана с замыканием, возвращаемым вызовом функции, и теперь я могу использовать его по своему усмотрению.
Теперь, чтобы замыкание работало должным образом в течение всего его жизненного цикла, нам нужно продлить время жизни захваченной переменной t
(в данном примере это целое число, но оно может быть значением любого типа). Насколько я знаю, в SML это стало возможным благодаря сборке мусора: замыкание сохраняет ссылку на захваченное значение, которое впоследствии удаляется сборщиком мусора при уничтожении замыкания.
Мой вопрос: в общем, является ли сборка мусора единственным возможным механизмом, обеспечивающим безопасность затворов (вызываемых в течение всей их жизни)?
Или каковы другие механизмы, которые могли бы обеспечить достоверность замыканий без сбора мусора: скопировать захваченные значения и сохранить их внутри замыкания? Ограничить время жизни самого замыкания, чтобы оно не могло быть вызвано после истечения его перехваченных переменных?
Каковы наиболее популярные подходы?
РЕДАКТИРОВАТЬ
Я не думаю, что приведенный выше пример можно объяснить / реализовать, скопировав захваченную переменную (и) в замыкание. В общем, захваченные переменные могут быть любого типа, например, они могут быть связаны с очень большим (неизменным) списком. Таким образом, при реализации было бы очень неэффективно копировать эти значения.
Для полноты картины приведу еще один пример использования ссылок (и побочных эффектов):
(* 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 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
Таким образом, переменные также могут быть захвачены по ссылке и остаются живыми после завершения вызова функции, создавшей их ( create_counter ()
).