Перейти к содержимому

Что такое замыкание в программировании

  • автор:

Замыкание (программирование)

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

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

У этого термина существуют и другие значения, см. Замыкание.

Эта статья или раздел нуждается в переработке.
Пожалуйста, улучшите статью в соответствии с правилами написания статей.

Замыкание (англ. closure ) в программировании — функция первого класса, в теле которой присутствуют ссылки на переменные, объявленные вне тела этой функции в окружающем коде и не являющиеся её параметрами. Говоря другим языком, замыкание — функция, которая ссылается на свободные переменные в своей области видимости.

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

Замыкание — это особый вид функции. Она определена в теле другой функции и создаётся каждый раз во время её выполнения. Синтаксически это выглядит как функция, находящаяся целиком в теле другой функции. При этом вложенная внутренняя функция содержит ссылки на локальные переменные внешней функции. Каждый раз при выполнении внешней функции происходит создание нового экземпляра внутренней функции, с новыми ссылками на переменные внешней функции.

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

Замыкание связывает код функции с её лексическим окружением (местом, в котором она определена в коде). Лексические переменные замыкания отличаются от глобальных переменных тем, что они не занимают глобальное пространство имён. От переменных в объектах они отличаются тем, что привязаны к функциям, а не объектам.

Примеры [ править | править код ]

Больше примеров смотрите в викиучебнике.

В языке 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 [2] [ править | править код ]

'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 замыкания — это

 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-параметр:

 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) int(1) [1]=>int(9) [2]=>int(16)> var_dump(power($list, 3)); // Выведет: array(3) int(1) [1]=>int(27) [2]=>int(64)> 

См. также [ править | править код ]

  • Лямбда-исчисление с типами
  • Подстановка
  • Модель акторов

Примечания [ править | править код ]

  • Найти и оформить в виде сносок ссылки на независимые авторитетные источники, подтверждающие написанное.

Что такое замыкание в программировании

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

Для начала потребуется два термина: область видимости и стек. Если нужно вспомнить, раскрывайте:

Что такое область видимости

Область видимости определяет, к каким переменным функция, команда или другая переменная может получить доступ, а к каким нет. Проще говоря, что каждая функция «видит» — видит ли она те переменные и объекты, которые созданы за её пределами? Видит ли она то, что вложено в функции, которые запускаются внутри неё?

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

Что такое стек

Стек — это как список задач при выполнении программы. Сначала компьютер исполняет один код, внутри которого появляется какая-то функция — это как будто отдельная программа. Компьютер откладывает текущую программу, делает себе в стеке пометку «вернуться сюда, когда доделаю новую программу» и исполняет эту новую функцию. Исполнил — смотрит в стек, куда вернуться. Возвращается туда.

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

Что такое замыкание

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

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

Звучит сложно, объясним на примерах.

Что такое замыкание в программировании

Пример замыкания в коде

Сделаем функцию, которая вернёт нам фразу «Привет, …» вместе с именем, которое мы в него отправим. Но сделаем это через замыкание — чтобы посмотреть, как именно всё устроено.

Обратите внимание на две переменные в конце: mike и maxim. По сути, эти переменные — ссылки на вызов результата функции hello(), но с конкретным параметром.

// внешняя функция function hello(name) < // внутренняя функция-замыкание // возвращаем её как результат работы внешней return function() < // внутренняя функция выводит сообщение на экран console.log("Привет, " + name); >> // создаём новые переменные, используя замыкание mike = hello("Миша"); maxim = hello("Максим") // запускаем внутреннюю функцию с нашими параметрами, просто указав имена переменной mike(); maxim();

Что такое замыкание в программировании

Ещё один пример, посложнее

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

Для примера сделаем замыкание и заведём две переменные — каждая будет выдавать сообщение со своим началом фразы, а продолжение будем передавать им в виде параметра:

// внешняя функция, у которой есть свой параметры function greeting(hi) < // внутренняя функция-замыкание, тоже со своим параметром // возвращаем её как результат работы внешней return function(name) < // внутренняя функция выводит сообщение на экран console.log(hi + ", " + name); >> // создаём новые переменные, используя замыкание morning = greeting("Доброе утро"); thnx = greeting("Спасибо за комментарий") // запускаем внутреннюю функцию с нашими параметрами, указав параметры вызова morning("коллеги"); thnx("Павел");

Что такое замыкание в программировании

Причём здесь область видимости

Из последнего примера видно, что объявление переменных происходит так:

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

Но как такое может быть, когда внутренняя функция использует переменную из области видимости внешней функции, которой уже нет?

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

Что такое замыкание в программировании

Зачем нужны замыкания

На замыканиях строится около половины алгоритмов в функциональном программировании. А ещё на них можно построить много разного:

  • изолировать логику выполнения фрагментов кода, если это не позволяют сделать встроенные возможности языка (как в JavaScript);
  • лучше структурировать код, особенно при организации функций, которые отличаются только несколькими элементами;
  • реализовать инкапсуляцию в тех языках, где её нет.

Что дальше

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

Замыкания в JavaScript для начинающих

Замыкания — это одна из фундаментальных концепций JavaScript, вызывающая сложности у многих новичков, знать и понимать которую должен каждый JS-программист. Хорошо разобравшись с замыканиями, вы сможете писать более качественный, эффективный и чистый код. А это, в свою очередь, будет способствовать вашему профессиональному росту.

Материал, перевод которого мы публикуем сегодня, посвящён рассказу о внутренних механизмах замыканий и о том, как они работают в JavaScript-программах.

Что такое замыкание?

Замыкание — это функция, у которой есть доступ к области видимости, сформированной внешней по отношению к ней функции даже после того, как эта внешняя функция завершила работу. Это значит, что в замыкании могут храниться переменные, объявленные во внешней функции и переданные ей аргументы. Прежде чем мы перейдём, собственно, к замыканиям, разберёмся с понятием «лексическое окружение».

Что такое лексическое окружение?

Понятие «лексическое окружение» или «статическое окружение» в JavaScript относится к возможности доступа к переменным, функциям и объектам на основе их физического расположения в исходном коде. Рассмотрим пример:

let a = 'global'; function outer() < let b = 'outer'; function inner() < let c = 'inner' console.log(c); // 'inner' console.log(b); // 'outer' console.log(a); // 'global' >console.log(a); // 'global' console.log(b); // 'outer' inner(); > outer(); console.log(a); // 'global'

Здесь у функции inner() есть доступ к переменным, объявленным в её собственной области видимости, в области видимости функции outer() и в глобальной области видимости. Функция outer() имеет доступ к переменным, объявленным в её собственной области видимости и в глобальной области видимости.

Цепочка областей видимости вышеприведённого кода будет выглядеть так:

Global < outer < inner >>

Обратите внимание на то, что функция inner() окружена лексическим окружением функции outer() , которая, в свою очередь, окружена глобальной областью видимости. Именно поэтому функция inner() может получить доступ к переменным, объявленным в функции outer() и в глобальной области видимости.

Практические примеры замыканий

Рассмотрим, прежде чем разбирать тонкости внутреннего устройства замыканий, несколько практических примеров.

▍Пример №1

function person() < let name = 'Peter'; return function displayName() < console.log(name); >; > let peter = person(); peter(); // 'Peter'

Здесь мы вызываем функцию person() , которая возвращает внутреннюю функцию displayName() , и сохраняем эту функцию в переменной peter . Когда мы, после этого, вызываем функцию peter() (соответствующая переменная, на самом деле, хранит ссылку на функцию displayName() ), в консоль выводится имя Peter .

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

▍Пример №2

function getCounter() < let counter = 0; return function() < return counter++; >> let count = getCounter(); console.log(count()); // 0 console.log(count()); // 1 console.log(count()); // 2

Тут, как и в предыдущем примере, мы храним ссылку на анонимную внутреннюю функцию, возвращённую функцией getCounter() , в переменной count . Так как функция count() представляет собой замыкание, она может обращаться к переменной counter функции getCount() даже после того, как функция getCounter() завершила работу.

Обратите внимание на то, что значение переменной counter не сбрасывается в 0 при каждом вызове функции count() . Может показаться, что оно должно сбрасываться в 0, как могло бы быть при вызове обычной функции, но этого не происходит.

Всё работает именно так из-за того, что при каждом вызове функции count() для неё создаётся новая область видимости, но существует лишь одна область видимости для функции getCounter() . Так как переменная counter объявлена в области видимости функции getCounter() , её значение между вызовами функции count() сохраняется, не сбрасываясь в 0.

Как работают замыкания?

До сих пор мы говорили о том, что такое замыкания, и рассматривали практические примеры. Теперь поговорим о внутренних механизмах JavaScript, обеспечивающих их работу.

Для того чтобы понять замыкания, нам нужно разобраться с двумя важнейшими концепциями JavaScript. Это — контекст выполнения (Execution Context) и лексическое окружение (Lexical Environment).

▍Контекст выполнения

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

В некий момент времени может выполняться код лишь в одном контексте выполнения (JavaScript — однопоточный язык программирования). Управление этими процессами ведётся с использованием так называемого стека вызовов (Call Stack).

Стек вызовов — это структура данных, устроенная по принципу LIFO (Last In, First Out — последним вошёл, первым вышел). Новые элементы можно помещать только в верхнюю часть стека, и только из неё же элементы можно изымать.

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

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

Пример контекста выполнения

Когда выполняется этот код, JavaScript-движок создаёт глобальный контекст выполнения для выполнения глобального кода, а когда встречает вызов функции first() , создаёт новый контекст выполнения для этой функции и помещает его в верхнюю часть стека.

Стек вызовов этого кода выглядит так:

Стек вызовов

Когда завершается выполнение функции first() , её контекст выполнения извлекается из стека вызовов и управление передаётся контексту выполнения, находящемуся ниже его, то есть — глобальному контексту. После этого будет выполнен оставшийся в глобальной области видимости код.

▍Лексическое окружение

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

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

Лексическое окружение содержит два компонента:

  • Запись окружения (environment record) — место, где хранятся объявления переменных и функций.
  • Ссылка на внешнее окружение (reference to the outer environment) — ссылка, позволяющая обращаться к внешнему (родительскому) лексическому окружению. Это — самый важный компонент, с которым нужно разобраться для того, чтобы понять замыкания.
lexicalEnvironment = < environmentRecord: < : , : > outer: < Reference to the parent lexical environment>>

Взглянем на следующий фрагмент кода:

let a = 'Hello World!'; function first() < let b = 25; console.log('Inside first function'); >first(); console.log('Inside global execution context');

Когда JS-движок создаёт глобальный контекст выполнения для выполнения глобального кода, он создаёт и новое лексическое окружение для хранения переменных и функций, объявленных в глобальной области видимости. В результате лексическое окружение глобальной области видимости будет выглядеть так:

globalLexicalEnvironment = < environmentRecord: < a : 'Hello World!', first : < reference to function object >> outer: null >

Обратите внимание на то, что ссылка на внешнее лексическое окружение ( outer ) установлена в значение null , так как у глобальной области видимости нет внешнего лексического окружения.

Когда движок создаёт контекст выполнения для функции first() , он создаёт и лексическое окружение для хранения переменных, объявленных в этой функции в ходе её выполнения. В результате лексическое окружение функции будет выглядеть так:

functionLexicalEnvironment = < environmentRecord: < b : 25, >outer: >

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

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

Подробный разбор примеров работы с замыканиями

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

▍Пример №1

Взгляните на данный фрагмент кода:

function person() < let name = 'Peter'; return function displayName() < console.log(name); >; > let peter = person(); peter(); // 'Peter'

Когда выполняется функция person() , JS-движок создаёт новый контекст выполнения и новое лексическое окружение для этой функции. Завершая работу, функция возвращает функцию displayName() , в переменную peter записывается ссылка на эту функцию.

Её лексическое окружение будет выглядеть так:

personLexicalEnvironment = < environmentRecord: < name : 'Peter', displayName: < displayName function reference>> outer: >

Когда функция person() завершает работу, её контекст выполнения извлекается из стека. Но её лексическое окружение остаётся в памяти, так как ссылка на него есть в лексическом окружении её внутренней функции displayName() . В результате переменные, объявленные в этом лексическом окружении, остаются доступными.

Когда вызывается функция peter() (соответствующая переменная хранит ссылку на функцию displayName() ), JS-движок создаёт для этой функции новый контекст выполнения и новое лексическое окружение. Это лексическое окружение будет выглядеть так:

displayNameLexicalEnvironment = < environmentRecord: < >outer: >

В функции displayName() нет переменных, поэтому её запись окружения будет пустой. В процессе выполнения этой функции JS-движок попытается найти переменную name в лексическом окружении функции.

Так как в лексическом окружении функции displayName() искомое найти не удаётся, поиск продолжится во внешнем лексическом окружении, то есть, в лексическом окружении функции person() , которое всё ещё находится в памяти. Там движок находит нужную переменную и выводит её значение в консоль.

▍Пример №2

function getCounter() < let counter = 0; return function() < return counter++; >> let count = getCounter(); console.log(count()); // 0 console.log(count()); // 1 console.log(count()); // 2

Лексическое окружение функции getCounter() будет выглядеть так:

getCounterLexicalEnvironment = < environmentRecord: < counter: 0, : < reference to function>> outer: >

Эта функция возвращает анонимную функцию, которая назначается переменной count .

Когда выполняется функция count() , её лексическое окружение выглядит так:

countLexicalEnvironment = < environmentRecord: < >outer: >

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

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

В результате лексическое окружение функции getCounter() после первого вызова функции count() будет выглядеть так:

getCounterLexicalEnvironment = < environmentRecord: < counter: 1, : < reference to function>> outer: >

При каждом следующем вызове функции count() JavaScript-движок создаёт новое лексическое окружение для этой функции и инкрементирует переменную counter , что приводит к изменениям в лексическом окружении функции getCounter() .

Итоги

В этом материале мы поговорили о том, что такое замыкания, и разобрали глубинные механизмы JavaScript, лежащие в их основе. Замыкания — одна из важнейших фундаментальных концепций JavaScript, её должен понимать каждый JS-разработчик. Понимание замыканий — это одна из ступеней пути к написанию эффективных и качественных приложений.

Уважаемые читатели! Если вы обладаете опытом JS-разработки — просим поделиться с начинающими практическими примерами применения замыканий.

  • Блог компании RUVDS.com
  • Веб-разработка
  • JavaScript

Замыкание (программирование)

Замыкание (англ. closure ) в программировании — процедура или функция, в теле которой присутствуют ссылки на переменные, объявленные вне тела этой функции и не в качестве её параметров (а в окружающем коде). Говоря другим языком, замыкание — это процедура или функция, которая ссылается на свободные переменные в своём лексическом контексте.

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

Замыкание — это особый вид функции. Она определена в теле другой функции и создаётся каждый раз во время её выполнения. В записи это выглядит как функция, находящаяся целиком в теле другой функции. При этом вложенная внутренняя функция содержит ссылки на локальные переменные внешней функции. Каждый раз при выполнении внешней функции происходит создание нового экземпляра внутренней функции, с новыми ссылками на переменные внешней функции.

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

Замыкание связывает код функции с её лексическим окружением (местом, в котором она определена в коде). Лексические переменные замыкания отличаются от глобальных переменных тем, что они не занимают глобальное пространство имён. От переменных в объектах они отличаются тем, что привязаны к функциям, а не объектам.

Реализации замыкания в языках программирования

Pascal

Пример работы замыканий на Pascal (Delphi c 2009 версии):

type TGenericFunction = reference to function: string; function Factory(const ASomeText: string):TGenericFunction; begin Result := function: string begin Result := ASomeText; end; end; var f1, f2: TGenericFunction; procedure TForm1.Button1Click(Sender: TObject); begin f1 := Factory('First'); f2 := Factory('Second'); Memo1.Lines.Add(f1); Memo1.Lines.Add(f2); end; 

В версиях начиная с 2009, этот код выведет в Memo строки First и Second. Когда переменной типа reference to *** присваивается совместимая по спецификации анонимная подпрограмма или метод, неявно создаётся и инициализируется экземпляр анонимного класса, с полями для хранения значений, используемых подпрограммой из контекста её объявления, методом выполнения (присвоенной подпрограммой) и счётчиком ссылок.

Scheme

Пример работы замыканий на 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 

C#

Анонимные методы в C# 2.0 могут замыкаться на локальный контекст:

int[] ary =  1, 2, 3 >; int x = 2; var ary1 = Array.ConvertAllint, int>(ary, delegate(int elem)  return elem * x; >); // // or.. var ary2 = Array.ConvertAllint, int>(ary, elem =>  return elem * x; >); //

Функция Array.ConvertAll преобразует один список/массив в другой, применяя для каждого элемента передаваемую ей в качестве параметра функцию.

В C# 3.0 введены лямбда-выражения, которые делают синтаксис анонимных методов более кратким и выразительным. Соответственно, они также поддерживают замыкания. То есть, замыкания в C# 3.0 практически аналогичны анонимным функциям из C# 2.0, но синтаксически более кратки. Вот тот же пример с применением лямбда-выражений в C# 3.0:

int[] ary =  1, 2, 3 >; var x = 2; var ary1 = ary.Select(elem => elem * x); //

Метод Select аналогичен методу Array.ConvertAll за тем исключением, что он принимает и возвращает IEnumerable.

C++

В языке C++ замыкание долгое время не поддерживалось. Однако новый стандарт языка C++11 вводит лямбда-функции и выражения, ограниченно поддерживающие замыкание:

functionint()> f()  int x = 0; return [=] () mutable return ++x; >; > auto fun = f(); for (int i = 0; i  5; ++i)  cout  ()  ; > 

VB.NET

В VB.NET 9.0 лямбда-функции могут быть только однострочными. Начиная с версии 10.0, можно использовать синтаксис для описания многострочных лямбда-функций.

Dim ary As Integer() = 1, 2, 3> Dim x As Integer = 2 ' VB.NET 9.0 - Dim ary1() As Integer = Array.ConvertAll(Of Integer, Integer)(ary, Function(elem) elem * x) ' VB.NET 10.0 - Dim ary2() As Integer = Array.ConvertAll(Of Integer, Integer)(ary, Function(elem) Return elem * x End Function) 

Ruby

Некоторые языки, такие как Ruby, позволяют выбирать различные способы замыканий по отношению к оператору возврата return . Вот пример на Ruby:

# ruby def foo f = Proc.new  return "return from foo from inside proc" > f.call # после вызова функции замыкания f осуществляется выход из foo # результатом работы функции foo является результат работы f замыкания return "return from foo" end def bar f = lambda  return "return from lambda" > f.call # после вызова функции замыкания f продолжается выполнение bar return "return from bar" end puts foo # печатает "return from foo from inside proc" puts bar # печатает "return from bar" 

И Proc.new , так же как и lambda , в этом примере — это способы создания замыкания, но семантика замыканий различна по отношению к оператору return .

PHP

PHP имеет встроенную поддержку замыканий начиная с версии 5.3. Пример замыкания. Локальная переменная $id будет увеличиваться при вызове возвращаемой функцией getAdder вложенной функции:

function getAdder()  $id = 1; return function() use (&$id) // use (&$id) для того чтобы передать в возвращаемую функцию внешнюю переменную $id return $id++; >; > $test= getAdder(); echo $test(); //1 $id увеличивается только после того, как возвращается, так как написано $id++ echo $test(); //2 echo $test(); //3 echo $test(); //4 

Для более ранних версий возможно использовать одноименный шаблон проектирования, который реализуется в библиотеке Николаса Нассара. P.S. Однако, до сих пор существует проблема с замыканиями в классах, в частности — для статических методов класса.

Java

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

class CalculationWindow extends JFrame  private JButton btnSave; . public final void calculateInSeparateThread(final URI uri)  // Выражение "new Thread() < . >" представляет собой пример анонимного класса. new Thread()  public void run()  // Имеет доступ к финальным (final) переменным: calculate(uri); // Имеет доступ к приватным членам содержащего класса: btnSave.setEnabled(true); > >.start(); > > 

Предполагалось, что версия Java-7 будет включать полную поддержку концепции замыканий, которые официально должны были называться «лямбда-выражения» (Lambda expressions), но этого не произошло. Теперь поддержка «лямбда-выражений» заявлена в версии Java-8 [2] .

Python

Пример с использованием замыканий и карринга:

# Реализация с помощью именованных функций: def taskerize(func_object): def unbound_closure(*args, **kwarg): def bound_closure(): return func_object(*args, **kwarg) return bound_closure return unbound_closure # Равносильная реализация с использованием lambda: taskerize = lambda func_object: ( lambda *args, **kwarg: ( lambda: func_object(*args, **kwarg) ) ) @taskerize # применение декоратора равнозначно записи testfunc = taskerize(testfunc) после объявления функции. def testfunc(a, b, c): return a + b * c f = testfunc(1, 2, 3) print f() # выведет 7 

Пример простого замыкания:

# Реализация с помощью именованных функций: def make_adder(x): def adder(n): return x + n # захват переменной "x" из внешнего контекста return adder # То же самое, но через безымянные функции: make_adder = lambda x: ( lambda n: ( x + n ) ) f = make_adder(10) print f(5) # 15 print f(-1) # 9 
# Функция с кучей аргументов (26 шт.), делающая что-то невразумительное. def longfunc(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z): print 'Меня вызвали с такими аргументами: ', a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z return a + b * c - d / e + f / g - h * i - (j * (k - l) + m) + (n * o) / (p - q + r) + (s * (t + (u * (v + w)))) - (x * y * z) def curry(func_object, *args): def innerfunc(*local_args): # в функции выполняется замыкание на args и func_object из внешнего контекста return func_object(*(args + local_args)) # а еще нам нужно прилепить в конец тех аргументов, что у нас были, новые return innerfunc # По уже сложившейся традиции — то же самое, только лямбдами: curry = lambda func_object, *args: ( lambda *local_args: ( func_object( *(args + local_args) ) ) ) # "достраиваем" функцию, как пожелаем. f1 = curry(longfunc, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100) f2 = curry(f1, 110, 120, 130, 140) f3 = curry(f2, 150, 160, 170, 180, 190, 200) f4 = curry(f3, 210) # не обязательно использовать функцию, к которой был применен карринг, только один раз. f5 = curry(f4, 220, 230, 240, 250, 260) # раз f5b = curry(f4, 220, 230, 240, 250) # два! f6b = curry(f5b, 260) print f5() # выведет 2387403 print f6b() # опять выведет 2387403 # контроль того, что карринг всё сделал верно (вызываем функцию со всеми её 26-ю параметрами): print longfunc( # перенос значений аргументов функций на несколько строк не имеет ничего общего с каррингом. Нет, правда. 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 210, 220, 230, 240, 250, 260 ) # да, опять выведет 2387403. 

JavaScript

В JavaScript областью видимости локальных переменных (объявляемых словом var) является тело функции, внутри которой они определены. [3]

Если вы объявляете функцию внутри другой функции, первая получает доступ к переменным и аргументам последней:

function outerFn(myArg)  var myVar; function innerFn()  // имеет доступ к myVar и myArg > > 

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

Рассмотрим пример — функцию, возвращающую количество собственных вызовов:

function createCounter()  var numberOfCalls = 0; return function()  return ++numberOfCalls; > > var fn = createCounter(); fn(); // 1 fn(); // 2 fn(); // 3 

Perl

Пример с использованием замыканий на Perl:

# возвращает анонимную функцию sub adder my $x = shift(); # в котором x - свободная переменная, sub my $y = shift(); # а y - связанная переменная $x + $y; >; > $add1 = adder(1); # делаем процедуру для прибавления 1 print $add1->(10); # печатает 11 $sub1 = adder(-1); # делаем процедуру для вычитания 1 print $sub1->(10); # печатает 9 

Lua

Пример с использованием замыканий на Lua:

function makeaddfunc(x) -- Возвращает новую анонимную функцию, которая добавляет x к аргументу return function(y) -- Когда мы ссылаемся на переменную x, которая вне текущей области, -- и время жизни которой меньше, чем этой анонимной функции, -- Lua создаёт замыкание. return x + y end end plustwo = makeaddfunc(2) print(plustwo(5)) -- Выводит 7 

Haskell

В Haskell замыкания используются повсеместно в виде частичного применения аргументов к функциям (также известного как каррирование).

sum3 :: Int -> Int -> Int -> Int sum3 x y z = x + y + z

Определение функции «sum3» напоминает следующий код на C:

int sum3(int x, int y, int z)  return(x + y + z); > 

На самом деле «sum3» эквивалентна функции «sum3_desugared», по определению которой видно, что «sum3_desugared» принимает один аргумент «x» и возвращает новую функцию со связанной переменной «x». Новая функция также принимает только один аргумент «y» и возвращает функцию от одного аргумента «z».

sum3_desugared :: Int -> Int -> Int -> Int sum3_desugared = \x -> \y -> \z -> x + y + z

Псевдоопределение таких функций выглядит следующим образом («bounded» — это некоторые фиксированные значения, которые неявно хранятся вместе с функциями):

sum2_closure :: Int -> Int -> Int sum2_closure = \y -> \z -> bounded_from_sum3 + y + z sum1_closure :: Int -> Int sum1_closure = \z -> bounded_from_sum3 + bounded_from_sum2 + z sum_value :: Int sum_value = bounded_from_sum3 + bounded_from_sum2 + bounded_from_sum1 sum2_with42 = sum3 42 sum2_with42 = \y -> \z -> 42 + y + z sum1_with42_with13 = sum3 42 13 sum1_with42_with13 = sum2_with42 13 sum1_with42_with13 = \z -> 42 + 13 + z sum_with42_with13_with66 = sum3 42 13 66 sum_with42_with13_with66 = sum2_with42 13 66 sum_with42_with13_with66 = sum1_with42_with13 66 sum_with42_with13_with66 = 42 + 13 + 66 

Такой подход очень часто применяется для создания «специализированных» функций из более общих:

— (&&) :: Bool -> Bool -> Bool — liftM2 :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c ( ) (Monad m) => m Bool -> m Bool -> m Bool ( ) = liftM2 (&&) — foldr :: (a -> b -> b) -> b -> [a] -> b custom_fold :: [a] -> b custom_fold = foldr k z where z = k x z =

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

Smalltalk

Пример с использованием замыкания на Smalltalk:

createClosureWithComparator: aComparator ^[ :each | ^ each < aComparator ] 

Выполнение метода создает замыкание, при использовании которого будет происходить сравнение произвольного аргумента each и связанного значения aComparator.

MATLAB

Пример реализации замыкания в MATLAB с использованием nested функций:

function d = some_func(a) function c = nested_func(b) c = a + b; end d = @nested_func; end >> f = some_func(10); f = @some_func/nested_func >> f(5) ans = 15 

Пример реализации замыкания в MATLAB с использованием анонимных функций:

>> f = @(x) @(y) x + y f = @(x)@(y)x+y >> ff = f(10) ff = @(y)x+y >> ff(5) ans = 15 

Objective-C

Пример реализации замыкания в Objective-c с использованием блоков(blocks):

typedef int (^Add)(int x,int y); Add addBlock = (^(int x, int y)  return x + y; >); int res = addBlock(5,6); NSLog(@"%i",res); >>11 

Common LISP

(defun photon-energy-common (planck) (lambda (freq) (* planck freq))) (setq photon-energy-hbar (photon-energy-common 1.054571726E-23)) (setq photon-energy-h (photon-energy-common 6.62606957E-23)) (funcall photon-energy-h 10E12) 

Go

package main import "fmt" func fibonacci() func() int  a, b := 0, 1 return func() int  a, b = b, a + b return b > > func main()  f := fibonacci() for i := 0; i < 10; ++i  fmt.Println(f()) > > 

См. также

  • Лямбда-исчисление с типами
  • Подстановка
  • Модель акторов

Примечания

  1. Blocks Can Be Closures — Containers, Blocks, and Iterators — Programming Ruby. The Pragmatic Programmer’s Guide.
  2. OpenJDK: Project Lambda
  3. Владимир Агафонкин Замыкания в JavaScript
  • Концепции языков программирования

Wikimedia Foundation . 2010 .

Полезное

Смотреть что такое «Замыкание (программирование)» в других словарях:

  • Замыкание (математика) — Замыкание: Термины В математике Замыкание (геометрия) Алгебраическое замыкание поля Оператор замыкания Замыкание отношения Замыкание относительно операции Замыкание (программирование) подпрограмма, сохраняющая контекст (привязку к переменным)… … Википедия
  • Замыкание множества — Замыкание: Термины В математике Замыкание (геометрия) Алгебраическое замыкание поля Оператор замыкания Замыкание отношения Замыкание относительно операции Замыкание (программирование) подпрограмма, сохраняющая контекст (привязку к переменным)… … Википедия
  • Замыкание — В Викисловаре есть статья «замыкание» Замыкание процесс или результат действия, сводящегося к ограничению или спрямлению чего либо … Википедия
  • Функциональное программирование на Питоне — Функциональное программирование является одной из парадигм, поддерживаемых языком программирования Python. Основными предпосылками для полноценного функционального программирования в Python являются: функции высших порядков, развитые средства… … Википедия
  • Функциональное программирование на Python — Функциональное программирование является одной из парадигм, поддерживаемых языком программирования Python. Основными предпосылками для полноценного функционального программирования в Python являются: функции высших порядков, развитые средства… … Википедия
  • Функциональная зависимость (программирование) — Функциональная зависимость концепция, лежащая в основе многих вопросов, связанных с реляционными базами данных, включая, в частности, их проектирование. Математически представляет бинарное отношение между множествами атрибутов данного… … Википедия
  • Продолжение (программирование) — Продолжение (англ. continuation) представляет состояние программы в определённый момент, которое может быть сохранено и использовано для перехода в это состояние. Продолжения содержат всю информацию, чтобы продолжить выполнения программы с… … Википедия
  • ECMAScript — Класс языка: мультипарадигменный: объектно ориентированное, обобщённое, функциональное, императивное, аспектно ориентированное, событийно ориентированное, прототипное программирование Появился в: 1995 Автор(ы) … Википедия
  • Лямбда-выражения — Лямбда выражение (в программировании) это специальный синтаксис для объявления анонимных функторов по месту их использования. Используя лямбда выражения, можно объявлять функции в любом месте кода. Обычно лямбда выражение допускает… … Википедия
  • Лямбда-выражение — В программировании лямбда или лямбда выражения это безымянная функция, объявляемая по месту ее непосредственного использования. Обычно лямбда допускает замыкание на лексический контекст, в котором она объявлена. Смотри также Лямбда исчисление… … Википедия
  • Обратная связь: Техподдержка, Реклама на сайте
  • �� Путешествия

Экспорт словарей на сайты, сделанные на PHP,
WordPress, MODx.

  • Пометить текст и поделитьсяИскать в этом же словареИскать синонимы
  • Искать во всех словарях
  • Искать в переводах
  • Искать в ИнтернетеИскать в этой же категории

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *