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

редактировать

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

Содержание
  • 1 История и этимология
  • 2 Анонимные функции
  • 3 Приложения
    • 3.1 Первоклассные функции
    • 3.2 Представление
    • 3.3 Другое использование
  • 4 Реализация и теория
  • 5 Различия в семантике
    • 5.1 Лексическая среда
    • 5.2 Пример 1: Ссылка на несвязанную переменную
    • 5.3 Пример 2: Случайная ссылка на связанную переменную
    • 5.4 Закрытие, оставляющее
  • 6 Подобно закрытие конструкции
    • 6.1 Обратные вызовы (C)
      • 6.1.1 Вложенная функция и указатель на функцию (C)
    • 6.2 Локальные классы и лямбда-функции (Java)
    • 6.3 Блоки (C, C ++, Objective-C 2.0)
    • 6.4 Делегаты (C #, VB.NET, D)
    • 6.5 Функциональные объекты (C ++)
    • 6.6 Встроенные агенты (Eiffel)
    • 6.7 C ++ Builder __closure зарезервированное слово
  • 7 См. Также
  • 8 Примечания
  • 9 Ссылки
  • 10 Внешние ссылки
История и этимология

Концепция замыканий была вырезана в 1960-х годах для механической оценки выражений в λ-исчислении. и впервые был полностью реализован в 1970 году язык Функция языка программирования PAL для поддержки функций класса с лексической областью видимости.

Питер Дж. Лэндин определил термин «закрытие» в 1964 году как имеющий часть среды и часть управления как используется его машиной SECD для вычислений выражений. Джоэл Мозес благодарит Ландина за введение «закрытие» для ссылок на лямбда-выражение, чьи открытые привязки (свободные переменные) имеют закрыто (или связано) лексической средой, что привело к закрытому выражению или закрытию. Это использование было принято Сассманом и Стилом, когда они определили Схему в 1975 году, лексически ограниченный вариант LISP, и получили широкое распространение..

Анонимные функции

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

Например, в следующем коде Python :

def f (x): def g (y): return x + y return g # Возвращает закрытие. def h (x): return lambda y: x + y # Вернуть замыкание. # Назначение замыканий для числа. a = f (1) b = h (1) # Использование замыканий, хранящихся в чисел. assert a (5) == 6 assert b (5) == 6 # Использование замыканий без предварительной привязки их к переменным. assert f (1) (5) == 6 # f (1) - это замыкание. assert h (1) (5) == 6 # h (1) - закрытие.

значения aи bявляются соединением в обоих случаях производятся путем возврата функций со свободной из включающей функции, так что свободная переменная связывается с параметром xвключающей функции. Замыкания в aи bфункционально идентичны. Единственная разница в реализации заключается в том, что в первом случае мы использовали вложенную функцию с именем g, во втором случае мы использовали анонимную вложенную функцию (используя используемое слово Python lambdaдля создания анонимной функции). Оригинальное имя, использованное при их оценке, не имеет значения.

Замыкание - это значение, как и любое другое значение. Его не нужно назначать, вместо этого его можно использовать напрямую, как показано в последних двух строках примеров. Такое использование можно рассматривать как «анонимное закрытие».

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

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

x = 1 nums = [1, 2, 3] def f (y): return x + y map (f, nums) map (lambda y: x + y, nums)

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

Этого также можно достичь с помощью затенения чис (что уменьшает объем нелокальной альтернативы), хотя на практике это менее распространено, поскольку полезно, и затенение не рекомендуется.. В этом примере fможно рассматривать как замыкание, потому что xв теле fпривязан к xв глобальном пространстве имен, а не xlocal для g:

x = 0 def f (y): return x + y def g (z): x = 1 # local x shadows global x return f (z) g (1) # оценивается как 1, а не 2
Приложения

замыкания связаны с языками, где функции являются использованием объектами первого класса, функции которых могут быть возвращены как результаты из функций высшего порядка или передаются в качестве аргументов другим вызовам функций; если функции со свободными переменными являются первоклассными, то возвращение одной свободным закрытием. Сюда входят языки функционального программирования, такие как Lisp и ML, а также многие современные многопарадигмальные языки, такие как Python и Ржавчина. Замыкания также часто используются с обратными вызовами, особенно для обработчиков событий, например, в JavaScript, где они используются для взаимодействия с динамической веб-страницей..

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

Функции первого класса

Функции первого класса

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

; Верните список всех книг, по крайней мере, с ПОРОГОМ проданных копий. (определить (порог самых продаваемых книг) (filter (lambda (book) (>= (book-sales book) threshold)) book-list))

В этом примере лямбда-выражение (lambda (book) (>= (book-sale book) threshold))появляется в функции бестселлеры. Когда лямбда-выражение оценивается, схема создает замыкание, состоит из кода для лямбда-выражения и ссылки на переменную порог, который является свободной внутри лямбда-выражения.

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

Вот тот же пример, переписанный на JavaScript, другом популярном языке с поддержкой закрытий:

// Возвращает список всех книг с как минимум «пороговым» проданным копий. функция bestSellingBooks (порог) {return bookList.filter (function (book) {return book.sales>= threshold;}); }

Здесь используется вместо слова functionвместо lambdaи метод Array.filterглобальные функции filter, но в остальном структуре и эффект кода такие же.

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

// Возвращает функцию, которая приближает производную f // с использованием интервала dx, который должен быть соответственно малым. производная функция (f, dx) {return function (x) {return (f (x + dx) - f (x)) / dx; }; }

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

Представление состояний

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

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

Другое использование

Замыкания имеют много применений:

  • Потому что замыкания задерживают вычисление, т.е. они ничего не «делают» до тех пор, пока они называются - их можно использовать для определения управляющих структур. Например, все стандартные управляющие структуры Smalltalk, включая ветви (if / then / else) и циклы (while и for), определяются с использованием объектов, которые включают методы замыкания. Пользователи могут также легко определять свои собственные управляющие структуры.
  • На языках, реализующих назначении, несколько функций могут быть созданы так близко в одной и той же среде, что позволяет им общаться в конфиденциально, изменяя эту среду. По схеме:
(define foo #f) (define bar #f) (let ((secret-message "none")) (set! Foo (lambda (msg) (set! Secret-message msg))) ( установить! bar (lambda () secret-message))) (display (bar)); выводит «none» (новая строка) (foo «встретимся у дока в полночь») (display (bar)); печатает «встретим меня у пристани вчь»
  • Замыкания могут установить для реализации объектных систем.

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

Реализация и теория

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

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

Это объясняет, почему обычно языки, которые включают в себя сборку мусора, также используют сборку мусора. Альтернативой является ручное управление памятью нелокальных чисел (выделение в куче и освобождение по завершении) или, если используется выделение стека, чтобы язык признал, что используется варианты использования приведут к неопределенному поведению, из-за исячих Указателей на указанные автоматические переменные, как в лямбда-выражениях в C ++ 11 вложенных функций в GNU C. Задача funarg (или проблема «функционального аргумента») способность реализации функций как объектов первого класса на языке программирования на основе стека, таком как C или C ++. Аналогично в D версии 1, что программист знает, что делать с делегатами и автоматическими локальными переменными, поскольку их ссылки будут недействительными после возврата из области определения (автоматическая локальная переменные находятся в стеке) - это по-прежнему позволяет использовать множество функциональных шаблонов, но для сложных случаев требуется явное распределение кучи для чисел. D версии 2 решила эту проблему, определив, какие переменные должны храниться в куче, и выполнила автоматическое выделение. D использует полярность по мере их передачи.

В строго функциональных языках с сохраненными данными (например, Erlang ) легко реализовать автоматическое управление памятью (сборку мусора), поскольку нет возможности циклов в ссылках на переменные. Например, в Erlang все аргументы и переменные размещаются в куче, но ссылки на них размещены в стеке. После возврата из функции ссылки остаются действительным. Очистка кучи выполняется инкрементным сборщиком мусора.

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

Схема, имеющая лексическую область видимости АЛГОЛ, с динамическими переменными и сборкой мусора, не имеет модели программирования стека и не страдает от ограничений языков, основанных на стеке. Замыкания естественным образом выражаются на схеме. Лямбда-форма включает в себя код, а свободные переменные ее окружения сохраняются в программе до тех пор, пока к ним возможен доступ, и поэтому они могут быть так же свободно, как и любое другое выражение схемы.

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

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

Различия в семантике

Лексическая среда

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

// ECMAScript var f, g; функция foo () {var x; f = функция () {return ++ x; }; g = функция () {return --x; }; х = 1; alert ('внутри foo, вызов f ():' + f ()); } foo (); // 2 alert ('вызов g ():' + g ()); // 1 (--x) alert ('вызов g ():' + g ()); // 0 (--x) alert ('вызов f ():' + f ()); // 1 (++ x) alert ('вызов функции f ():' + f ()); // 2 (++ x)

Функция fooи замыкания, на которые ссылаются переменные fи g, используют одну и ту же относительную ячейку памяти, обозначенную локальная переменная x.

В некоторых случаях вышеуказанное поведение может быть нежелательным, и необходимо связать другое лексическое замыкание. Опять же в ECMAScript это можно сделать с помощью Function.bind ().

Пример 1: Ссылка на несвязанную переменную

var module = {x: 42, getX: function () {return this.x ; }} var unboundGetX = module.getX; console.log (unboundGetX ()); // Функция вызывается в глобальной области видимости // выдает undefined, поскольку 'x' не указан в глобальной области. var boundGetX = unboundGetX.bind (модуль); // указываем объектный модуль как закрытие console.log (boundGetX ()); // генерирует 42

Пример 2: Случайная ссылка на связанную переменную

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

var elements = document.getElementsByTagName ('a'); // Неправильно: e привязан к функции, содержащей цикл for, а не к закрытию "handle" for (var e in elements) {e.onclick = function handle () {alert (e.id);}}

Здесь снова переменная eдолжна быть связанаобластью блока с помощью handle.bind (this)или ключевого слова let.

С другой стороны, многие функциональные языки, такие как ML, связывают переменные напрямую со значениями. В этом случае, поскольку нет возможности изменить значение после привязки, нет необходимости разделять состояние между замыканиями - они просто используют одни и те же значения. Это часто называется захватом переключателя «по значению». Локальные и анонимные классы Java также попадают в эту категорию - они требуют, чтобы захваченные локальные переменные были final, что также означает, что нет необходимости специально использовать состояние.

Некоторые языки позволяют выбирать между захватом значений или ее местоположением. Например, в C ++ 11 захваченные переменные либо объявляются с помощью [], что означает захват по ссылке, либо с помощью [=], что означает захват по значению.

Еще одно подмножество, ленивых функциональных языков, таких как Haskell, привязывает переменные к результатам будущих вычислений, а не к значениям. Рассмотрим этот пример в Haskell:

- Haskell foo :: Fractional a =>a ->a ->(a ->a) foo xy = (\ z ->z + r), где r = x / yf: : Fractional a =>a ->af = foo 1 0 main = print (f 123)

Привязка r, захваченное замыкание, определенным в функциях foo, относится к вычислению (x / y)- которое в этом случае приводит к делению на ноль. Однако, поскольку используется ошибка вычисления, а не значение, проявляется только при вызове замыкания и пытается использовать захваченную привязку.

Закрытие, вызывающее

Еще больше различийется в поведении других конструкций с лексической областью видимости, таких как возврат, перерыви продолжитьутверждение. Такие конструкции могут рассматриваться с точки зрения вызова escape-продолжения, установленного включающим управляющим оператором (в случае breakи continueтакая интерпретация требует, чтобы конструкция рассматривались с точки зрения рекурсивных функций функций). На некоторых языках, таких как ECMAScript, returnотносится к продолжению, установленному закрытому, наиболее внутренне по отношению к инструкциям - таким образом, returnвнутри замыкания передает управление коду, который назвал это. В Smalltalk внешне похожий оператор ^вызывает продолжение выхода, установленное для вызова метода, игнорируя продолжения выхода промежуточных вложенных замыканий. Эскейп-продолжение замыкания может быть вызвано в Smalltalk только неявно, достигнув конца замыкания. Следующие примеры в ECMAScript и Smalltalk подчеркивают разницу:

"Smalltalk" foo | хз | хз: = # (1 2 3 4). хз делать: [: х | ^ х]. ^ 0 bar Transcript show: (self foo printString) "печатает 1"
// функция ECMAScript foo () {var xs = [1, 2, 3, 4]; xs.forEach (функция (x) {return x;}); возврат 0; } alert (foo ()); // выводит 0

Приведенные выше фрагменты кода будут вести себя по-разному, потому что оператор Smalltalk ^и оператор JavaScript returnне аналогичны. В примере ECMAScript return xоставит внутреннее замыкание, чтобы начать новую итерацию цикла forEach, тогда как в примере Smalltalk ^ xпрервет цикл и возврат из метода foo.

Common Lisp предоставляет конструкцию, которая может выражать любое из указанных действий: Lisp (return-from foo x)ведет себя как Smalltalk ^x, тогда как Lisp (return-from nil x)ведет себя как JavaScript return x. Следовательно, Smalltalk позволяет захваченному продолжению пережить степень, в которой его можно успешно активировать. Рассмотрим:

"Smalltalk" foo ^ [: x | ^ x] бар | f | f: = self foo. f значение: 123 "ошибка!"

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

Некоторые языки, такие как Ruby, позволяют программисту выбирать способ захвата return. Пример на Ruby:

# Ruby # Закрытие с использованием Proc def foo f = Proc.new {return "return from foo from inside proc"} f.call # control оставляет foo здесь return "return from foo" end # Использование закрытия лямбда def bar f = lambda {return "return from lambda"} f.call # элемент управления здесь не оставляет bar return "return from bar" end put foo # выводит "return from foo изнутри proc" помещает bar # prints "return from bar "

И Proc.new, и lambdaв этом примере выполнения способа создания замыкания, но семантика созданного таким образом замыкания отличается от оператор.

В схеме определение и область действия управляющего оператора returnявляются явными (и только для примера с произвольным названием «возврат»). Ниже представлен прямой перевод примера Ruby.

; Схема (определить call / cc call-with-current-continue) (define (foo) (call / cc (lambda (return) (define (f) (return «return from foo from inside proc»)) (f); control; оставляет здесь foo (return "return from foo")))) (define (bar) (call / cc (lambda (return) (define (f) (call / cc (lambda (return) (return) return from lambda))))) (f); элемент управления не оставляет бар здесь (return «возврат из бара»)))) (display (foo)); печатает «возврат из foo изнутри процедуры» (новая строка) (отображение (панель)); печатает "возврат из бара"
Конструкции, замыкание

На некоторых языках есть функции, имитирующие замыкание. На таких языках, как Java, C ++, Objective-C, C #, VB.NET и D, эти функции являются результатом объектно-ориентированной парадигмы языка.

Обратные вызовы (C)

Некоторые библиотеки C контейнер обратные вызовы. Иногда это реализуется путем предоставления двух значений при регистрации обратного вызова в библиотеке: указателя функций и отдельного указателя void *на произвольные данные по выбору пользователя. Когда библиотека функция обратного вызова, она передает указатель данных. Это позволяет функции обратного вызова поддерживать состояние и ссылаться на информацию, полученную во время регистрации в библиотеке. Идиома похожа на замыкание по функциональности, но не по синтаксису. Указатель void *не является типобезопасным, поэтому эта идиома C отличается от типобезопасных замыканий в C #, Haskell или ML.

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

Вложенная функция и указатель функции (C)

С расширением gcc можно использовать вложенную функцию, а указатель функции может имитировать замыкание, при условии, что содержащая функция выполняет не выход. Пример ниже неверен:

#include typedef int (* fn_int_to_int) (int); // тип функции int->int fn_int_to_int adder (int number) {int add (int value) {return value + number; } и вернуться; // Оператор и здесь необязателен, потому что имя функции в C - это указатель, указывающий на себя} int main (void) {fn_int_to_int add10 = adder (10); printf ("% d \ n", add10 (1)); возврат 0; }

Локальные классы и лямбда-функции (Java)

Java позволяет определять классы внутри методов. Они называются локальными классами. Когда такие классы не именуются, они как известны анонимные классы (или анонимные внутренние классы). Локальный класс (именованный или анонимный) может ссылаться на имена в лексически включающих классах или на переменные только для чтения (отмеченные как final) в лексически включающем методе.

класс CalculationWindow расширяет JFrame {частный изменчивый результат int;... public void calculateInSeparateThread (final URI uri) {// Выражение "new Runnable () {...}" - это анонимный класс, реализующий интерфейс Runnable. new Thread (new Runnable () {void run () {// Он может читать конечные локальные переменные: calculate (uri); // Он может получить доступ к закрытым полям включающего класса: result = result + 10;}}).start (); }}

Захват конечныхпеременных позволяет вам захватывать переменные по значению. Даже если переменная, которую вы хотите захватить, не является final, вы всегда можете скопировать ее во временную переменную finalнепосредственно перед классом.

Захват по ссылке можно эмулировать, используя finalссылку на изменяемый контейнер, например, одноэлементный массив. Локальный класс не сможет изменить значение самой ссылки на контейнер, но он сможет изменить содержимое контейнера.

С появлением лямбда-выражений в Java 8 закрывает выполнение вышеуказанного кода как:

class CalculationWindow расширяет JFrame {private volatile int result;... public void calculateInSeparateThread (final URI uri) {// Код () ->{/ * code * /} - это закрытие. новый поток (() ->{вычислить (uri); результат = результат + 10;}). Начало (); }}

Локальные классы - это один из типов внутреннего класса, объявленных в теле метода. Java объявлены внутренние классы, которые являются нестатическими членами включающего класса. Обычно их называют просто «внутренними классами». Они в теле включающего класса и имеют полный доступ к переменным экземплярам включающего класса. Из-за их привязки к этим переменным экземплярам внутренний класс может быть создан только с явной привязкой к экземпляру включающего класса с использованием специального синтаксиса.

открытый класс EnclosingClass {/ * Определить внутренний класс * / открытый класс InnerClass {public int incrementAndReturnCounter () {счетчик возврата ++; }} частный счетчик int; {counter = 0; } public int getCounter () {счетчик возврата; } public static void main (String args) {EnclosingClass enclosingClassInstance = new EnclosingClass (); / * Создание внутреннего класса с привязкой к экземпляру * / EnclosingClass.InnerClass innerClassInstance = enclosingClassInstance.new InnerClass (); for (int i = enclosingClassInstance.getCounter (); (i = innerClassInstance.incrementAndReturnCounter ()) < 10;) { System.out.println(i); } } }

После выполнения этого напечатает целые числа от 0 до 9. Остерегайтесь путать этот тип класса с вложенным классом, который объявлен таким же образом с сопутствующим Использование модификатора static; они не имеют желаемого эффекта, а они не имеют просто классы без привязки, определенные во включающем классе.

Начиная с Java 8, Java поддерживает функции как объекты первого класса. -выражения этой формы считаются типа Функция, где T - домен, а U - тип изображения. Выражение может быть вызвано с его .apply (T t)method, но не со стандартным вызовом метода.

public static void main (String args) {Function length = s ->s.length (); System.out.println (length.apply ("Hello, world!")); // выведет 13.}

Blocks (C, C ++, Objective-C 2.0)

Apple представила block, форму закрытия, как нестандартную ndard в C, C ++, Objective-C 2.0 и в Mac OS X 10.6 «Snow Leopard» и iOS 4.0. Apple сделала свою реализацию доступной для компиляторов GCC и clang.

Указатели на блокировку и блокирующие литералы отмечены ^. Нормальные локальные переменные фиксируются по значению при создании блока и доступны только для чтения внутри блока. Переменные, которые должны быть захвачены по ссылке, помечены __block. Блоки, которые должны сохраняться за пределами области, в которой они созданы, возможно, потребуется скопировать.

typedef int (^ IntBlock) (); IntBlock downCounter (int start) {__block int i = start; return [[^ int () {return i--; } копия] автозапуск]; } IntBlock f = downCounter (5); NSLog (@ "% d", f ()); NSLog (@ "% d", f ()); NSLog (@ "% d", f ());

Делегаты (C #, VB.NET, D)

C# анонимные методы и лямбда-выражения поддерживают закрытие:

var data = new {1, 2, 3, 4}; var multiplier = 2; var result = data.Select (x =>x * multiplier);

Visual Basic.NET, который имеет много языковых функций, аналогичных C #, также поддерживает лямбда-выражения с замыканиями:

Dim data = {1, 2, 3, 4} Dim multiplier = 2 Dim result = data.Select (Function (x) x * multiplier)

В D замыкания реализуются делегатами, указателем функции в паре с указателем контекста (например, экземпляром класса или кадром стека на куча на случай замыканий).

автоматический тест1 () {int a = 7; return delegate () {возврат + 3; }; // построение анонимного делегата} auto test2 () {int a = 20; int foo () {вернуть + 5; } // внутренняя функция return foo; // другой способ создания делегата} void bar () {auto dg = test1 (); dg (); // = 10 // хорошо, test1.a закрывается и все еще существует dg = test2 (); dg (); // = 25 // хорошо, test2.a закрывается и все еще существует}

D версии 1, имеет ограниченную поддержку закрытия. Например, приведенный выше код не будет работать правильно, потому что переменная a находится в стеке, и после возврата из test () ее больше нельзя использовать (скорее всего, вызов foo через dg () вернет ' случайное целое число). Это может быть решено путем явного выделения переменной «a» в куче или использования структур или классов для хранения всех необходимых закрытых переменных и создания делегата из метода, реализующего тот же код. Замыкания могут быть переданы другим функциям, если они используются только тогда, когда указанные значения еще действительны (например, использование вызова другой функции с закрытием в качестве обратного вызова), и полезны для написания универсального кода обработки данных, поэтому на практике это ограничение часто не является проблемой.

Это ограничение было исправлено в D версии 2 - переменная 'a' будет автоматически выделена в куче, поскольку она используется во внутренней функции, и эта функция может выйти из текущей области (через присвоение в dg или вернуться). Любые другие локальные переменные (или аргументы), которые не указаны, являются делегатами, которые не выходят за пределы текущей области, остаются в стеке, что проще и быстрее, чем распределение кучи. То же самое верно и для методов внутреннего класса, которые относятся к переменным функциям.

Функциональные объекты (C ++)

C ++ позволяет определять функциональные объекты путем перегрузки operator (). Эти объекты себя как функции функционального языка программирования. Они могут быть созданы во время выполнения и могут содержать состояние, но они не захватывают неявно локальные переменные, как это делают замыкание. Начало с версии 2011 г., язык C ++ также поддерживает замыкания, которые предоставляют собой тип функционального объекта, автоматически создаваемое из специальной языковой конструкции, называемой лямбда-выражением. Замыкание C ++ может быть выполнено в соответствии с сохранением файлов, используемых как членами закрывающего объекта, либо по ссылке. В последнем случае, если закрывающий объект выходит из области действия объекта, вызов его operator ()вызывает неопределенное поведение, возникновение замыкания C ++ не продлевает время жизни их контекста.

void foo (строка мое имя) {int y; вектор n; //... auto i = std :: find_if (n.begin (), n.end (), // это лямбда-выражение: [] (const string s) {return s! = myname s. size ()>y;}); // 'i' теперь либо 'n.end ()', либо указать на первую строку в 'n' //, которая не равна 'myname' и длина которой больше 'y'}

Встроенные агенты (Eiffel)

Eiffel включает встроенные агенты, определяющие замыкания. Встроенный агент - это объект, представляющий код, предоставляемый путем предоставления встроенным. Например, в

ok_button.click_event.subscribe (agent (x, y: INTEGER) do map.country_at_coordinates (x, y).display end)

аргумент subscribe- это агент, представляющий с двумя аргументами; процедура находит страну по соответствующим координатам и отображает ее. Весь агент «подписан» на тип события click_eventдля определенных кнопок, так что всякий раз, когда на кнопке возникает данный тип событий - потому что нажал кнопку - процедура будет с координатами мыши, передаваем в качестве аргументов для хи y.

. Основное ограничение агентов Eiffel, которое отличает их от замыканий на других языках, заключается в том, что они не могут ссылаться на локальные переменные из области окружающей среды. Это проектное решение помогает избежать неисправности электрического замыкания - ли это должно быть последнее значение, полученное при создании агента? Из тела агента можно получить доступ только к Текущий(ссылка на текущий, аналога этотв Java), его функции и аргументам самого агента. Значения внешних локальных чисел можно передать, предоставив агенту дополнительные закрытые операнды.

C ++ Builder __closure зарезервированное слово

Embarcadero C ++ Builder предоставляет зарезервированное слово __closure, чтобы предоставить указатель на метод с синтаксисом, аналогичные функции.

В стандартном C вы написать typedef для указателя на типовые функции, используя следующий синтаксис:

typedef void (* TMyFunctionPointer) (void);

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

typedef void (__closure * TMyMethodPointer) ();
См.
Примечания
Ссылки
Внешние ссылки
Последняя правка сделана 2021-05-15 12:09:40
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте