Что такое «Закрытие»?

Я задал вопрос о Currying и о закрытии. Что такое закрытие? Как это относится к карри?

Область переменных

Когда вы объявляете локальную переменную, эта переменная имеет область видимости. Обычно локальные переменные существуют только внутри блока или функции, в которых вы их объявляете.

function() { var a = 1; console.log(a); // works } console.log(a); // fails 

Если я попытаюсь получить доступ к локальной переменной, большинство языков будут искать ее в текущей области, а затем вверх по родительским областям до тех пор, пока они не достигнут корневой области.

 var a = 1; function() { console.log(a); // works } console.log(a); // works 

Когда блок или функция выполняется, его локальные переменные больше не нужны и обычно выходят из памяти.

Так мы обычно ожидаем, что все будет работать.

Закрытие – это постоянная локальная переменная

Закрытие – это постоянная область действия, которая относится к локальным переменным даже после того, как выполнение кода вышло из этого блока. Языки, поддерживающие закрытие (например, JavaScript, Swift и Ruby), позволят вам сохранить ссылку на область действия (включая ее родительские области), даже после того, как блок, в котором были объявлены эти переменные, завершил выполнение, если вы сохраните ссылку на что блок или функция где-то.

Объект scope и все его локальные переменные привязаны к функции и будут сохраняться до тех пор, пока эта функция сохраняется.

Это дает нам возможность переносимости. Мы можем ожидать, что любые переменные, которые были в области видимости, когда функция была впервые определена, все еще остаются в области действия, когда мы позже вызываем эту функцию, даже если мы вызываем эту функцию в совершенно другом контексте.

Например

Вот простой пример в JavaScript, который иллюстрирует точку:

 outer = function() { var a = 1; var inner = function() { console.log(a); } return inner; // this returns a function } var fnc = outer(); // execute outer to get inner fnc(); 

Здесь я определил функцию внутри функции. Внутренняя функция получает доступ ко всем внешним переменным внешней функции, включая a . Переменная a находится в области внутренней функции.

Обычно, когда функция выходит, все ее локальные переменные сдуваются. Однако, если мы вернем внутреннюю функцию и присвоим ей переменную fnc , чтобы она сохранялась после выхода outer , все переменные, которые были в области, когда inner была определена, также сохраняются . Переменная a была закрыта – она ​​находится в пределах замыкания.

Обратите внимание, что переменная a полностью fnc для fnc . Это способ создания частных переменных на функциональном языке программирования, таком как JavaScript.

Как вы могли угадать, когда я вызываю fnc() он печатает значение a , которое равно «1».

На языке без закрытия переменная a была бы мусором, собранным и выброшенным при выходе из outer функции. Вызов fnc вызвал бы ошибку, потому a больше не существует.

В JavaScript переменная a сохраняется, поскольку область переменных создается при первом объявлении функции и сохраняется до тех пор, пока функция продолжает существовать.

a относится к сфере outer . Область inner имеет родительский указатель на область outer . fnc – переменная, которая указывает на inner . a сохраняется, пока fnc сохраняется. a находится в закрытии.

Я приведу пример (в JavaScript):

 function makeCounter () { var count = 0; return function () { count += 1; return count; } } var x = makeCounter(); x(); returns 1 x(); returns 2 ...etc... 

Что делает эта функция makeCounter, она возвращает функцию, которую мы назвали x, которая будет подсчитывать каждый раз при ее вызове. Поскольку мы не предоставляем никаких параметров х, он должен как-то помнить счет. Он знает, где его найти, основываясь на том, что называется лексическим охватом – оно должно смотреть на то место, где оно определено, чтобы найти значение. Это «скрытое» значение – это то, что называется замыканием.

Вот мой пример карри:

 function add (a) { return function (b) { return a + b; } } var add3 = add(3); add3(4); returns 7 

Вы можете видеть, что при вызове add с параметром a (который равен 3) это значение содержится в закрытии возвращаемой функции, которую мы определяем как add3. Таким образом, когда мы вызываем add3, он знает, где найти значение для выполнения добавления.

Ответ Кайла довольно хорош. Я думаю, что единственным дополнительным пояснением является то, что закрытие в основном представляет собой снимок стека в точке, где создается lambda-функция. Затем, когда функция повторно выполняется, стек возвращается в это состояние перед выполнением функции. Таким образом, Кайл упоминает, что скрытое значение ( count ) доступно, когда выполняется lambda-функция.

Закрытие – это функция, которая может ссылаться на состояние в другой функции. Например, в Python это использует закрытие «inner»:

 def outer (a): b = "variable in outer()" def inner (c): print a, b, c return inner # Now the return value from outer() can be saved for later func = outer ("test") func (1) # prints "test variable in outer() 1 

Для облегчения понимания закрытий было бы полезно изучить, как они могут быть реализованы на процедурном языке. Это объяснение будет следовать упрощенной реализации замыканий на Схеме.

Для начала я должен ввести понятие пространства имен. Когда вы вводите команду в интерпретатор Схемы, она должна оценивать различные символы в выражении и получать их значение. Пример:

 (define x 3) (define y 4) (+ xy) returns 7 

Определяющие выражения сохраняют значение 3 в пятне для x и значение 4 в пятне для y. Затем, когда мы вызываем (+ xy), интерпретатор просматривает значения в пространстве имен и может выполнять операцию и возвращать 7.

Однако на Схеме есть выражения, которые позволяют временно переопределить значение символа. Вот пример:

 (define x 3) (define y 4) (let ((x 5)) (+ xy)) returns 9 x returns 3 

То, что делает ключевое слово let, представляет новое пространство имен с x как значение 5. Вы заметите, что он все еще может видеть, что y равно 4, что возвращает сумму, равную 9. Вы также можете видеть, что как только выражение закончилось x возвращается к состоянию 3. В этом смысле x временно маскируется локальным значением.

Аналогичные понятия имеют процедурные и объектно-ориентированные языки. Всякий раз, когда вы объявляете переменную в функции с тем же именем, что и глобальная переменная, вы получаете тот же эффект.

Как это реализовать? Простым способом является связанный список – голова содержит новое значение, а хвост содержит старое пространство имен. Когда вам нужно искать символ, вы начинаете во главе и прокладываете себе путь по хвосту.

Теперь перейдем к реализации первоclassных функций на данный момент. Более или менее, функция представляет собой набор команд для выполнения, когда функция называется кульминацией в возвращаемом значении. Когда мы читаем в функции, мы можем хранить эти инструкции за кулисами и запускать их при вызове функции.

 (define x 3) (define (plus-x y) (+ xy)) (let ((x 5)) (plus-x 4)) returns ? 

Мы определяем x как 3, а плюс-x – его параметр, y, плюс значение x. Наконец, мы называем плюс-x в среде, где x был замаскирован новым x, этот оценивается 5. Если мы просто сохраняем операцию (+ xy) для функции plus-x, поскольку мы находимся в контексте от x равно 5, результат будет равен 9. Это то, что называется динамическим охватом.

Однако Scheme, Common Lisp и многие другие языки имеют то, что называется лексическим охватом – помимо хранения операции (+ xy) мы также сохраняем пространство имен в этой конкретной точке. Таким образом, когда мы просматриваем значения, мы можем видеть, что x в этом контексте действительно 3. Это закрытие.

 (define x 3) (define (plus-x y) (+ xy)) (let ((x 5)) (plus-x 4)) returns 7 

Таким образом, мы можем использовать связанный список для хранения состояния пространства имен во время определения функции, что позволяет нам получать доступ к переменным из охватывающих областей, а также предоставлять нам возможность локально маскировать переменную, не затрагивая остальную часть программа.

Прежде всего, вопреки тому, что говорит вам большинство людей, закрытие не является функцией ! Так что это?
Это набор символов, определенных в «окружающем контексте» функции (известный как ее окружение ), которые делают его CLOSED-выражением (то есть выражением, в котором каждый символ определен и имеет значение, поэтому его можно оценить).

Например, когда у вас есть функция JavaScript:

 function closed(x) { return x + 3; } 

это замкнутое выражение, потому что все символы, встречающиеся в нем, определены в нем (их значения понятны), поэтому вы можете его оценить. Другими словами, он является самодостаточным .

Но если у вас есть такая функция:

 function open(x) { return x*y + 3; } 

это открытое выражение, потому что в нем есть символы, которые в нем не определены. А именно, y . Рассматривая эту функцию, мы не можем сказать, что такое y и что это значит, мы не знаем ее значения, поэтому мы не можем оценить это выражение. Т.е. мы не можем назвать эту функцию, пока не скажем, что в ней подразумевается. Это y называется свободной переменной .

Это задает определение, но это определение не является частью функции – оно определяется где-то еще, в его «окружающем контексте» (также известном как среда ). По крайней мере, это то, на что мы надеемся: P

Например, он может быть определен глобально:

 var y = 7; function open(x) { return x*y + 3; } 

Или он может быть определен в функции, которая его обертывает:

 var global = 2; function wrapper(y) { var w = "unused"; return function(x) { return x*y + 3; } } 

Часть среды, которая дает свободные переменные в выражении, их значения, является замыканием . Он называется так, потому что он превращает открытое выражение в замкнутое , предоставляя эти отсутствующие определения для всех его свободных переменных , чтобы мы могли его оценить.

В приведенном выше примере внутренняя функция (которую мы не дали имени, потому что она нам не нужна) является открытым выражением, потому что переменная y в ней свободна – ее определение вне функции, в функции, которая обертывает Это. Среда для этой анонимной функции представляет собой набор переменных:

 { global: 2, w: "unused", y: [whatever has been passed to that wrapper function as its parameter `y`] } 

Теперь закрытие является той частью этой среды, которая закрывает внутреннюю функцию, предоставляя определения для всех ее свободных переменных . В нашем случае единственной свободной переменной во внутренней функции было y , поэтому закрытие этой функции является этим подмножеством своей среды:

 { y: [whatever has been passed to that wrapper function as its parameter `y`] } 

Два других символа, определенные в среде, не являются частью закрытия этой функции, потому что она не требует их запуска. Они не нужны, чтобы закрыть его.

Подробнее об этой теории здесь: https://stackoverflow.com/a/36878651/434562

Стоит отметить, что в приведенном выше примере функция-обертка возвращает свою внутреннюю функцию как значение. Момент, который мы называем этой функцией, может быть удален во времени с момента, когда функция была определена (или создана). В частности, его функция обертки больше не работает, а ее параметры, которые были в стеке вызовов, больше не существуют: P Это создает проблему, потому что внутренней функции требуется, чтобы она была там, когда она вызывается! Другими словами, для того, чтобы как-то пережить функцию обертки, необходимо, чтобы переменные были закрыты, и там, где это необходимо. Поэтому внутренняя функция должна сделать моментальный снимок этих переменных, которые делают его закрытие и сохраняют их где-то в безопасности для последующего использования. (Где-то вне стека вызовов.)

И поэтому люди часто путают термин закрытие как особый тип функции, который может делать такие снимки внешних переменных, которые они используют, или структуру данных, используемую для хранения этих переменных позже. Но я надеюсь, что теперь вы понимаете, что они не являются самим закрытием – это просто способы реализовать закрытие на языке программирования или языковые механизмы, которые позволяют переменным от закрытия функции быть там, когда это необходимо. Существует множество неправильных представлений о закрытии, которые (излишне) делают эту тему более запутанной и сложной, чем она есть на самом деле.

Вот реальный пример того, почему Closures подбрасывает задницу … Это прямо из моего кода Javascript. Позвольте мне проиллюстрировать.

 Function.prototype.delay = function(ms /*[, arg...]*/) { var fn = this, args = Array.prototype.slice.call(arguments, 1); return window.setTimeout(function() { return fn.apply(fn, args); }, ms); }; 

И вот как вы его используете:

 var startPlayback = function(track) { Player.play(track); }; startPlayback(someTrack); 

Теперь представьте, что вы хотите, чтобы воспроизведение запустилось, например, через 5 секунд после этого fragmentа кода. Ну, это легко с delay и закрытием:

 startPlayback.delay(5000, someTrack); // Keep going, do other things 

Когда вы вызываете delay с 5000 мс, первый fragment запускается и сохраняет переданные аргументы в его закрытии. Затем через 5 секунд, когда происходит обратный вызов setTimeout , закрытие все еще сохраняет эти переменные, поэтому оно может вызывать исходную функцию с исходными параметрами.
Это тип currying или функция украшения.

Без замыканий вам придется каким-то образом поддерживать эти переменные, находящиеся вне функции, таким образом засоряя код вне функции чем-то, что логически принадлежит внутри него. Использование закрытий может значительно улучшить качество и читаемость вашего кода.

В обычной ситуации переменные привязаны по правилу определения области охвата: Локальные переменные работают только внутри определенной функции. Закрытие – способ нарушать это правило временно для удобства.

 def n_times(a_thing) return lambda{|n| a_thing * n} end 

в приведенном выше коде lambda(|n| a_thing * n} является закрытием, потому что a_thing ссылается a_thing (анонимным создателем функции).

Теперь, если вы поместите полученную анонимную функцию в переменную функции.

 foo = n_times(4) 

foo нарушит нормальное правило видимости и начнет использовать 4 внутри.

 foo.call(3) 

возвращает 12.

Функции, не содержащие свободных переменных, называются чистыми функциями.

Функции, содержащие одну или несколько свободных переменных, называются замыканиями.

 var pure = function pure(x){ return x // only own environment is used } var foo = "bar" var closure = function closure(){ return foo // foo is a free variable from the outer environment } 

src: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure

Короче говоря, указатель на функцию – это просто указатель на местоположение в базе кода программы (например, счетчик программ). В то время как Closure = указатель функции + рамка стека .

,

ТЛ; др

Закрытие – это функция и ее область, назначенная (или используемая как) переменная. Таким образом, закрытие имени: область действия и функция вложены и используются точно так же, как и любой другой объект.

В глубине Wikipedia описание стиля

Согласно Википедии, закрытие :

Методы реализации привязки имени с лексической привязкой на языках с первоclassными функциями.

Что это значит? Давайте рассмотрим некоторые определения.

Я объясню закрытие и другие связанные определения, используя этот пример:

 function startAt(x) { return function (y) { return x + y; } } var closure1 = startAt(1); var closure2 = startAt(5); console.log(closure1(3)); // 4 (x == 1, y == 3) console.log(closure2(3)); // 8 (x == 5, y == 3) 

Вот еще один пример реальной жизни и использование языка скриптов, популярного в играх – Lua. Мне нужно было немного изменить способ работы библиотечной функции, чтобы избежать проблемы с недоступностью stdin.

 local old_dofile = dofile function dofile( filename ) if filename == nil then error( 'Can not use default of stdin.' ) end old_dofile( filename ) end 

Значение old_dofile исчезает, когда этот блок кода заканчивает его область (потому что он локальный), однако это значение было заключено в закрытие, поэтому новая переопределенная функция dofile может получить к ней доступ, или, скорее, копию, сохраненную вместе с функцией как «повышать стоимость».

От Lua.org :

Когда функция записывается в другую функцию, она имеет полный доступ к локальным переменным из закрывающей функции; эта функция называется лексическим охватом. Хотя это может показаться очевидным, это не так. Лексическая область обзора, плюс первоclassные функции, является мощной концепцией на языке программирования, но несколько языков поддерживают эту концепцию.

Если вы находитесь в мире Java, вы можете сравнить замыкание с функцией-членом classа. Посмотрите на этот пример

 var f=function(){ var a=7; var g=function(){ return a; } return g; } 

Функция g является замыканием: g закрывает a . Таким образом, g можно сравнить с функцией-членом, a можно сравнить с полем classа и функцией f с classом.

Закрытия. Когда у нас есть функция, определенная внутри другой функции, внутренняя функция имеет доступ к переменным, объявленным во внешней функции. Ключи лучше всего объясняются примерами. В листинге 2-18 вы можете видеть, что внутренняя функция имеет доступ к переменной (variableInOuterFunction) из внешней области. Переменные во внешней функции были закрыты (или связаны) внутренней функцией. Следовательно, термин «замыкание». Понятие само по себе достаточно просто и достаточно интуитивно.

 Listing 2-18: function outerFunction(arg) { var variableInOuterFunction = arg; function bar() { console.log(variableInOuterFunction); // Access a variable from the outer scope } // Call the local function to demonstrate that it has access to arg bar(); } outerFunction('hello closure!'); // logs hello closure! 

источник: http://index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf

Interesting Posts
Давайте будем гением компьютера.