Jump to content

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

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

История и этимология

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

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

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

Сассман и Абельсон также использовали термин «замыкание» в 1980-х годах во втором, несвязанном с ним значении: свойство оператора, который добавляет данные в структуру данных , иметь возможность добавлять вложенные структуры данных. Такое использование термина происходит от использования математики , а не от предыдущего использования в информатике. Такое совпадение в терминологии авторы считают «неудачным». [7]

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

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

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

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

def f(x):
    def g(y):
        return x + y
    return g  # Return a closure.

def h(x):
    return lambda y: x + y  # Return a closure.

# Assigning specific closures to variables.
a = f(1)
b = h(1)

# Using the closures stored in variables.
assert a(5) == 6
assert b(5) == 6

# Using closures without binding them to variables first.
assert f(1)(5) == 6  # f(1) is the closure.
assert h(1)(5) == 6  # h(1) is the closure.

ценности 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 в глобальном пространстве имен, а не в x местный для g:

x = 0

def f(y):
    return x + y

def g(z):
    x = 1  # local x shadows global x
    return f(z)

g(1)  # evaluates to 1, not 2

Приложения

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

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

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

Первоклассные функции

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

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

; Return a list of all books with at least THRESHOLD copies sold.
(define (best-selling-books threshold)
  (filter
    (lambda (book)
      (>= (book-sales book) threshold))
    book-list))

В этом примере лямбда-выражение (lambda (book) (>= (book-sales book) threshold)) появляется внутри функции best-selling-books. Когда вычисляется лямбда-выражение, Scheme создает замыкание, состоящее из кода лямбда-выражения и ссылки на threshold переменная, которая является свободной переменной внутри лямбда-выражения.

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

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

// Return a list of all books with at least 'threshold' copies sold.
function bestSellingBooks(threshold) {
  return bookList.filter(book => book.sales >= threshold);
}

Оператор стрелки => используется для определения выражения стрелочной функции , а Array.filter метод [8] вместо глобального filter функция, но в остальном структура и эффект кода такие же.

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

// Return a function that approximates the derivative of f
// using an interval of dx, which should be appropriately small.
function derivative(f, dx) {
  return x => (f(x + dx) - f(x)) / dx;
}

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

Государственное представительство

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

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

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

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

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

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

  • Поскольку замыкания задерживают вычисление (т. е. они ничего не «делают» до тех пор, пока их не вызовут), их можно использовать для определения управляющих структур. Например, все стандартные структуры управления Smalltalk , включая ветви (if/then/else) и циклы ( while и for ), определяются с использованием объектов, методы которых допускают замыкания. Пользователи также могут легко определять свои собственные структуры управления.
  • В языках, реализующих присваивание, можно создавать несколько функций, расположенных близко к одной и той же среде, что позволяет им взаимодействовать конфиденциально, изменяя эту среду. В схеме:
(define foo #f)
(define bar #f)

(let ((secret-message "none"))
  (set! foo (lambda (msg) (set! secret-message msg)))
  (set! bar (lambda () secret-message)))

(display (bar)) ; prints "none"
(newline)
(foo "meet me by the docks at midnight")
(display (bar)) ; prints "meet me by the docks at midnight"
  • Замыкания можно использовать для реализации объектных систем. [9]

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

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

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

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

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

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

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

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

Scheme , который имеет систему лексической области видимости, подобную ALGOL, с динамическими переменными и сборкой мусора, не имеет модели стекового программирования и не страдает от ограничений языков, основанных на стеке. Замыкания естественным образом выражаются в Scheme. Лямбда-форма заключает в себе код, а свободные переменные ее среды сохраняются в программе до тех пор, пока к ним возможен доступ, и поэтому их можно использовать так же свободно, как и любое другое выражение схемы. [ нужна ссылка ]

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

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

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

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

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

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

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

// Javascript
var f, g;
function foo() {
  var x;
  f = function() { return ++x; };
  g = function() { return --x; };
  x = 1;
  alert('inside foo, call to f(): ' + f());
}
foo();  // 2
alert('call to g(): ' + g());  // 1 (--x)
alert('call to g(): ' + g());  // 0 (--x)
alert('call to f(): ' + f());  // 1 (++x)
alert('call to f(): ' + f());  // 2 (++x)

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

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

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

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

[13]

var module = {
  x: 42,
  getX: function() {return this.x; }
}
var unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// emits undefined as 'x' is not specified in global scope.

var boundGetX = unboundGetX.bind(module); // specify object module as the closure
console.log(boundGetX()); // emits 42

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

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

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

var elements = document.getElementsByTagName('a');
// Incorrect: e is bound to the function containing the 'for' loop, not the closure of "handle"
for (var e of 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 x y = (\z -> z + r)
          where r = x / y

f :: Fractional a => a -> a
f = foo 1 0

main = print (f 123)

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

Закрытие выезда

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

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

"Smalltalk"
foo
  | xs |
  xs := #(1 2 3 4).
  xs do: [:x | ^x].
  ^0
bar
  Transcript show: (self foo printString) "prints 1"
// ECMAScript
function foo() {
  var xs = [1, 2, 3, 4];
  xs.forEach(function (x) { return x; });
  return 0;
}
alert(foo()); // prints 0

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

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

"Smalltalk"
foo
    ^[ :x | ^x ]
bar
    | f |
    f := self foo.
    f value: 123 "error!"

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

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

# Ruby

# Closure using a Proc
def foo
  f = Proc.new { return "return from foo from inside proc" }
  f.call # control leaves foo here
  return "return from foo"
end

# Closure using a lambda
def bar
  f = lambda { return "return from lambda" }
  f.call # control does not leave bar here
  return "return from bar"
end

puts foo # prints "return from foo from inside proc"
puts bar # prints "return from bar"

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

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

; Scheme
(define call/cc call-with-current-continuation)

(define (foo)
  (call/cc
   (lambda (return)
     (define (f) (return "return from foo from inside proc"))
     (f) ; control leaves foo here
     (return "return from foo"))))

(define (bar)
  (call/cc
   (lambda (return)
     (define (f) (call/cc (lambda (return) (return "return from lambda"))))
     (f) ; control does not leave bar here
     (return "return from bar"))))

(display (foo)) ; prints "return from foo from inside proc"
(newline)
(display (bar)) ; prints "return from bar"

Замыкающие конструкции

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

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

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

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

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

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

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

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

Благодаря расширению GNU Compiler Collection (GCC) вложенная функция [15] можно использовать, а указатель функции может эмулировать замыкания при условии, что функция не выходит из содержащейся области. Следующий пример неверен, потому что adder является определением верхнего уровня (в зависимости от версии компилятора оно может дать правильный результат, если скомпилировано без оптимизации, т. е. при -O0):

#include <stdio.h>

typedef int (*fn_int_to_int)(int); // type of function int->int

fn_int_to_int adder(int number) {
  int add (int value) { return value + number; }
  return &add; // & operator is optional here because the name of a function in C is a pointer pointing on itself
}

int main(void) {
  fn_int_to_int add10 = adder(10);
  printf("%d\n", add10(1));
  return 0;
}

Но переезд adder (и, по желанию, typedef) в main делает его действительным:

#include <stdio.h>

int main(void) {
  typedef int (*fn_int_to_int)(int); // type of function int->int
  
  fn_int_to_int adder(int number) {
    int add (int value) { return value + number; }
    return add;
  }
  
  fn_int_to_int add10 = adder(10);
  printf("%d\n", add10(1));
  return 0;
}

Если это выполнено, теперь печатается 11 как и ожидалось.

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

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

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

class CalculationWindow extends JFrame {
    private volatile int result;
    // ...
    public void calculateInSeparateThread(final URI uri) {
        // The expression "new Runnable() { ... }" is an anonymous class implementing the 'Runnable' interface.
        new Thread(
            new Runnable() {
                void run() {
                    // It can read final local variables:
                    calculate(uri);
                    // It can access private fields of the enclosing class:
                    result = result + 10;
                }
            }
        ).start();
    }
}

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

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

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

class CalculationWindow extends JFrame {
    private volatile int result;
    // ...
    public void calculateInSeparateThread(final URI uri) {
        // The code () -> { /* code */ } is a closure.
        new Thread(() -> {
            calculate(uri);
            result = result + 10;
        }).start();
    }
}

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

public class EnclosingClass {
    /* Define the inner class */
    public class InnerClass {
        public int incrementAndReturnCounter() {
            return counter++;
        }
    }

    private int counter;
    {
        counter = 0;
    }

    public int getCounter() {
        return counter;
    }

    public static void main(String[] args) {
        EnclosingClass enclosingClassInstance = new EnclosingClass();
        /* Instantiate the inner class, with binding to the instance */
        EnclosingClass.InnerClass innerClassInstance =
            enclosingClassInstance.new InnerClass();

        for (int i = enclosingClassInstance.getCounter();
             (i = innerClassInstance.incrementAndReturnCounter()) < 10;
             /* increment step omitted */) {
            System.out.println(i);
        }
    }
}

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

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

public static void main(String[] args) {
    Function<String, Integer> length = s -> s.length();

    System.out.println( length.apply("Hello, world!") ); // Will print 13.
}

Блоки (C, C++, Objective-C 2.0)

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

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

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

typedef int (^IntBlock)();

IntBlock downCounter(int start) {
	 __block int i = start;
	 return [[ ^int() {
		 return i--;
	 } copy] autorelease];
}

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 замыкания реализуются делегатами, указателем функции в паре с указателем контекста (например, экземпляром класса или кадром стека в куче в случае замыканий).

auto test1() {
    int a = 7;
    return delegate() { return a + 3; }; // anonymous delegate construction
}

auto test2() {
    int a = 20;
    int foo() { return a + 5; } // inner function
    return &foo;  // other way to construct delegate
}

void bar() {
    auto dg = test1();
    dg();    // =10   // ok, test1.a is in a closure and still exists

    dg = test2();
    dg();    // =25   // ok, test2.a is in a closure and still exists
}

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

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

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

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

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

void foo(string myname) {
    int y;
    vector<string> n;
    // ...
    auto i = std::find_if(n.begin(), n.end(),
               // this is the lambda expression:
               [&](const string& s) { return s != myname && s.size() > y; }
             );
    // 'i' is now either 'n.end()' or points to the first string in 'n'
    // which is not equal to 'myname' and whose length is greater than 'y'
}

Встроенные агенты (Эйфель)

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

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

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

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

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

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

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

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

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

typedef void (*TMyFunctionPointer)( void );

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

typedef void (__closure *TMyMethodPointer)();

См. также

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

Примечания

[ редактировать ]
  1. ^ Функция может храниться как ссылка на функцию, например указатель на функцию .
  2. ^ Эти имена обычно относятся к значениям, изменяемым переменным или функциям, но также могут быть другими объектами, такими как константы, типы, классы или метки.
  1. ^ Сассман и Стил. «Схема: интерпретатор расширенного лямбда-исчисления». «... структура данных, содержащая лямбда-выражение, и среда, которая будет использоваться, когда это лямбда-выражение применяется к аргументам». ( Викиисточник )
  2. ^ Тернер, Дэвид А. (2012). «Некоторые истории языков функционального программирования» (PDF) . Международный симпозиум по тенденциям функционального программирования . Конспекты лекций по информатике. Том. 7829. Спрингер. С. 1–20 Утверждение о М-выражениях см. в § 12, § 2, примечание 8. дои : 10.1007/978-3-642-40447-4_1 . ISBN  978-3-642-40447-4 .
  3. ^ Ландин, П.Дж. (январь 1964 г.). «Механическая оценка выражений» (PDF) . Компьютерный журнал . 6 (4): 308–320. дои : 10.1093/comjnl/6.4.308 .
  4. ^ Моисей, Джоэл (июнь 1970 г.). «Функция FUNCTION в LISP, или Почему проблему FUNARG следует называть проблемой среды». Бюллетень ACM Sigsam (15): 13–27. дои : 10.1145/1093410.1093411 . hdl : 1721.1/5854 . S2CID   17514262 . Памятка AI 199. Полезной метафорой различия между FUNCTION и QUOTE в LISP является представление QUOTE как пористого или открытого покрытия функции, поскольку свободные переменные уходят в текущую среду. ФУНКЦИЯ действует как закрытое или непористое покрытие (отсюда и термин «закрытие», используемый Ландином). Таким образом, мы говорим об «открытых» лямбда-выражениях (функции в LISP обычно являются лямбда-выражениями) и «закрытых» лямбда-выражениях. [...] Мой интерес к проблеме окружающей среды начался, когда Ландин, глубоко разбиравшийся в этой проблеме, посетил Массачусетский технологический институт в 1966–67 годах. Затем я понял соответствие между списками FUNARG, которые являются результатами оценки «закрытых» лямбда-выражений в LISP и Lambda Closures ISWIM .
  5. ^ Викстрем, Оке (1987). Функциональное программирование с использованием стандартного ML . Прентис Холл. ISBN  0-13-331968-7 . Причина, по которой это называется «замыканием», заключается в том, что выражение, содержащее свободные переменные, называется «открытым» выражением, и, связывая с ним привязки его свободных переменных, вы закрываете его.
  6. ^ Сассман, Джеральд Джей ; Стил, Гай Л. младший (декабрь 1975 г.). Схема: интерпретатор расширенного лямбда-исчисления (отчет). Памятка AI 349.
  7. ^ Абельсон, Гарольд ; Сассман, Джеральд Джей ; Сассман, Джули (1996). Структура и интерпретация компьютерных программ . МТИ Пресс. стр. 98–99. ISBN  0-262-51087-1 .
  8. ^ "массив.фильтр" . Центр разработчиков Mozilla . 10 января 2010 г. Проверено 9 февраля 2010 г.
  9. ^ «Re: FP, OO и отношения. Кто-нибудь превосходит остальных?» . 29 декабря 1999 года. Архивировано из оригинала 26 декабря 2008 года . Проверено 23 декабря 2008 г.
  10. ^ Комитет по стандартам лямбда-выражений и замыканий C++. 29 февраля 2008 г.
  11. ^ «6.4 Вложенные функции» . Руководство GCC . Если вы попытаетесь вызвать вложенную функцию по ее адресу после выхода из содержащей функции, начнется ад. Если вы попытаетесь вызвать его после выхода из содержащего уровня области видимости и если он ссылается на некоторые переменные, которые больше не входят в область видимости, вам может повезти, но рисковать неразумно. Однако если вложенная функция не ссылается ни на что, выходящее за пределы области видимости, вы можете быть в безопасности.
  12. ^ Основы семантики актеров будут сохраняться. Докторская диссертация по математике в Массачусетском технологическом институте. Июнь 1981 года.
  13. ^ «Функция.прототип.bind()» . Веб-документы MDN . Проверено 20 ноября 2018 г.
  14. ^ «Замыкания» . Веб-документы MDN . Проверено 20 ноября 2018 г.
  15. ^ «Вложенные функции» .
  16. ^ «Лямбда-выражения» . Учебники по Java .
  17. ^ «Вложенные, внутренние, члены и классы верхнего уровня» . Блог Oracle Джозефа Д. Дарси . Июль 2007 г. Архивировано из оригинала 31 августа 2016 г.
  18. ^ «Пример внутреннего класса» . Учебные пособия по Java: Изучение языка Java: классы и объекты .
  19. ^ «Вложенные классы» . Учебные пособия по Java: Изучение языка Java: классы и объекты .
  20. ^ «Темы программирования блоков» . Apple Inc., 8 марта 2011 г. Проверено 8 марта 2011 г.
  21. ^ Бенгтссон, Иоахим (7 июля 2010 г.). «Программирование с использованием блоков C на устройствах Apple» . Архивировано из оригинала 25 октября 2010 года . Проверено 18 сентября 2010 г.
  22. ^ Полную документацию можно найти по адресу http://docwiki.embarcadero.com/RADStudio/Rio/en/Closure.
[ редактировать ]
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: 1ad9c69ecf6fa6642f5d6317717efd22__1721401860
URL1:https://arc.ask3.ru/arc/aa/1a/22/1ad9c69ecf6fa6642f5d6317717efd22.html
Заголовок, (Title) документа по адресу, URL1:
Closure (computer programming) - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)