Замыкание (программирование)
Замыкание (англ. closure) в программировании — это различные методики передачи в функцию первого класса (она же callback) информации о переменных вне её тела[1]. Другими словами, это передача в функцию окольным путём некоторой информации, при каких обстоятельствах её вызвали — и наоборот, функция может окольным путём передать наружу, что случилось при её исполнении. Простейший пример: операция сортировки массива принимает функцию сравнения, а в эту функцию передаётся, по какому полю сортировать.
Замыкание, так же как и экземпляр объекта, есть способ представления функциональности и данных, связанных и упакованных вместе.
Существуют различные методики передачи в функцию замыканий:
- Глобальная переменная, с той скидкой, что функция теряет реентерабельность — стандартная библиотека Си.
- Дополнительный параметр, который гарантированно передаётся неизменным и позволяет упаковать небольшой объект или передать по указателю большой — низкоуровневые библиотеки наподобие WinAPI[2];
- Объект с операцией «вызов», создаваемый каждый раз,— Си++, Java;
- Особый вид функции — PHP.
- Метод объекта, вместе с указателем на сам объект — Delphi.
В любом случае ссылки на переменные внешней функции действительны внутри вложенной функции до тех пор, пока работает вложенная функция, даже если внешняя функция закончила работу, и переменные вышли из области видимости.[3]
Замыкание связывает код функции с её лексическим окружением (местом, в котором она определена в коде). Лексические переменные замыкания отличаются от глобальных переменных тем, что они не занимают глобальное пространство имён. От переменных в объектах они отличаются тем, что привязаны к функциям, а не объектам.
Некоторые из назначений
Основное назначение замыкания (в любом виде) — условно «библиотечный» код вызывает «пользовательскую» функцию (либо находящуюся на другом слое абстракции), и потому последняя обязана иметь нейтральную сигнатуру, не выдающую, какими сторонними данными происходит обмен. В частности, для механизмов «при наступлении события вызвать пользовательскую функцию»[4], «для каждого объекта вызвать пользовательскую функцию»[2], «провести сортировку в пользовательском порядке»[5]… Условно (замыкание сделано дополнительным параметром на манер WinAPI):
функция добавитьВСписок(x : целое, замык : объект)
(замык → список).добавь(x)
y : список
пройтиПростыеЧисла(1000, добавитьВСписок, y)
Однако языковая поддержка замыканий позволила решать и другие задачи.
- Программирование в структурном стиле. В частности, генераторы данных и итераторы[4].
y : список пройтиПростыеЧисла(1000, [y] x : целое → y.добавь(x) )
- Программирование в функциональном стиле — функции первого порядка, карринг, композиция функций[4].
функция форматироватьВалюту(валюта : строка, точность : целое) вернуть [валюта, точность] x : дробное → вернуть форматировать(x, точность) + " " + валюта форматироватьЕвро = форматироватьВалюту("€", 2)
- Один из способов сделать скрытые (доступные только объекту-владельцу) данные, к тому же не пересекающиеся ни с одной глобальной переменной[4].
- Чтобы оформить небольшой объект как функцию — для кэширования предыдущих вызовов, фильтрации ввода и другого[4].
Примеры
Больше примеров смотрите в викиучебнике.
В языке Scheme
(define (make-adder n) ; возвращает замкнутое лямбда-выражение
(lambda (x) ; в котором x - связанная переменная,
(+ x n) ; а n - свободная (захваченная из внешнего контекста)
)
)
(define add1 (make-adder 1)) ; делаем процедуру для прибавления 1
(add1 10) ; вызываем её, возвращает 11
(define sub1 (make-adder -1)); делаем процедуру для вычитания 1
(sub1 10) ; вызываем её, возвращает 9
В языке JavaScript[6]
'use strict';
const add = function(x) {
return function(y) {
const z = x + y;
console.log(x + '+' + y + '=' + z);
return z;
};
};
const res = add(3)(6); // вернёт 9 и выведет в консоль 3+6=9
console.log(res);
Этот же код в версии ECMAScript2015 с использованием «стрелочных функций»:
'use strict';
const add = x => y => {
const z = x + y;
console.log(x + '+' + y + '=' + z);
return z;
};
const res = add(3)(6); // вернёт 9 и выведет в консоль 3+6=9
console.log(res);
Пояснение: в JavaScript сочетание => является оператором объявления стрелочной функции, см например https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Functions/Arrow_functions. Здесь в константу add помещается функция от аргумента x, результатом которой будет являться другая функция, а именно функция от аргумента y, результат которой вычисляется приведённым в фигурных скобках блоком кода. Этот блок кода опирается на аргумент y своей функции и на замыкание, создаваемое для аргумента x внешней функции.
При вызове add(3)(6) функция, хранящаяся в переменной add, вызывается с аргументом 3 и возвращает функцию, завязанную на значение 3 в замыкании x.
Далее в рамках такого обращения эта функция выполняется с аргументом y = 6 и возвращает 9.
Можно сделать рекурсивное замыкание:
'use strict';
const add = x => y => {
const z = x + y;
console.log(x + '+' + y + '=' + z);
return add(z);
};
const res = add(1)(4)(6)(9);
console.log(res);
/* 1+4=5
5+6=11
11+9=20
[Function]*/
Когда JS-код работает — локальные переменные хранятся в scope. В JavaScript локальные переменные могут оставаться в памяти даже после того, как функция вернула значение.
Все функции в JavaScript это замыкания, то есть всегда, когда создается функция — всегда создается замыкание, хоть и зачастую оно пустое, так как функции обычно из объявления контекста как правило ничего не используют. Но нужно понимать разницу между созданием замыкания и созданием нового scope-объекта: замыкание (функция + ссылка на текущую цепочку scope-объектов) создается при определении функции, но новый scope-объект создается (и используется для модификации цепочки scope-объектов замыкания) при каждом вызове функции.
В языке PHP
В PHP замыкания — это анонимные функции, особые конструкции, которые позволяют описывать функции, не имеющие определённых имён.
<?php
function add($x)
{
return function ($y) use ($x) { // <-- анонимная функция (замыкание)
return $x + $y;
}; // <-- эта точка с запятой здесь нужна!
}
echo add(3)(5) . PHP_EOL; // Выведет: 8
$f = add(3);
var_dump($f); // Выведет: object(Closure)
echo $f(6) . PHP_EOL; // Выведет: 9
В PHP наследование переменных из родительской области видимости осуществляется с помощью конструкции use путем явного указания имен наследуемых переменных.
Другой пример с передачей замыкания в метод, где ожидается callable-параметр:
<?php
function power($arr, $exp)
{
// переменная $func будет хранить ссылку на объект класса Closure, который описывает наше замыкание
$func = function ($el) use ($exp) {
return $el ** $exp;
};
return array_map($func, $arr);
}
$list = [1, 3, 4];
var_dump(power($list, 2)); // Выведет: array(3) {[0]=>int(1) [1]=>int(9) [2]=>int(16)}
var_dump(power($list, 3)); // Выведет: array(3) {[0]=>int(1) [1]=>int(27) [2]=>int(64)}
См. также
- Анонимная функция
- Лямбда-исчисление с типами
- Подстановка
- Модель акторов
Примечания
- ↑ Closures — JavaScript | MDN
- ↑ 1 2 Функция EnumWindows (winuser.h) - Win32 apps | Microsoft Learn
- ↑ Blocks Can Be Closures — Containers, Blocks, and Iterators — Programming Ruby. The Pragmatic Programmer’s Guide. Дата обращения: 29 сентября 2011. Архивировано 23 сентября 2011 года.
- ↑ 1 2 3 4 5 https://medium.com/deno-the-complete-reference/10-use-cases-of-closures-in-javascript-98fe0eab36db
- ↑ std::sort - cppreference.com
- ↑ Closure: Function closures and storing data in function scope. — 2018-01-08. Архивировано 29 ноября 2019 года.