~~~~~~~~~~~~~~~~~~~~ Arc.Ask3.Ru ~~~~~~~~~~~~~~~~~~~~~ 
Номер скриншота №:
✰ 1EFC36FC324F6096D4689B94EAF55DCE__1715617080 ✰
Заголовок документа оригинал.:
✰ Closure (computer programming) - Wikipedia ✰
Заголовок документа перевод.:
✰ Замыкание (компьютерное программирование) — Википедия ✰
Снимок документа находящегося по адресу (URL):
✰ https://en.wikipedia.org/wiki/Closure_(computer_science) ✰
Адрес хранения снимка оригинал (URL):
✰ https://arc.ask3.ru/arc/aa/1e/ce/1efc36fc324f6096d4689b94eaf55dce.html ✰
Адрес хранения снимка перевод (URL):
✰ https://arc.ask3.ru/arc/aa/1e/ce/1efc36fc324f6096d4689b94eaf55dce__translat.html ✰
Дата и время сохранения документа:
✰ 16.06.2024 09:49:23 (GMT+3, MSK) ✰
Дата и время изменения документа (по данным источника):
✰ 13 May 2024, at 19:18 (UTC). ✰ 

~~~~~~~~~~~~~~~~~~~~~~ Ask3.Ru ~~~~~~~~~~~~~~~~~~~~~~ 
Сервисы Ask3.ru: 
 Архив документов (Снимки документов, в формате HTML, PDF, PNG - подписанные ЭЦП, доказывающие существование документа в момент подписи. Перевод сохраненных документов на русский язык.)https://arc.ask3.ruОтветы на вопросы (Сервис ответов на вопросы, в основном, научной направленности)https://ask3.ru/answer2questionТоварный сопоставитель (Сервис сравнения и выбора товаров) ✰✰
✰ https://ask3.ru/product2collationПартнерыhttps://comrades.ask3.ru


Совет. Чтобы искать на странице, нажмите Ctrl+F или ⌘-F (для MacOS) и введите запрос в поле поиска.
Arc.Ask3.ru: далее начало оригинального документа

Замыкание (компьютерное программирование) — Википедия 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    # Возвращает замыкание. 

  def   h  (  x  ): 
     return   лямбда   y  :   x   +   y    # Вернуть замыкание. 

  # Назначение конкретных замыканий переменным. 
  a   =   f  (  1  ) 
 b   =   h  (  1  ) 

 # Использование замыканий, хранящихся в переменных. 
  Assert   a  (  5  )   ==   6 
 Assert   b  (  5  )   ==   6 

 # Использование замыканий без предварительной привязки их к переменным. 
  утверждать   f  (  1  )(  5  )   ==   6    # f(1) — замыкание. 
  утверждать, что   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  ): 
     вернуть   x   +   y 

 map  (  f  ,   nums  ) 
 map  (  лямбда   y  :   x   +   y  ,   nums  ) 

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

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

x   =   0 

 def   f  (  y  ): 
     return   x   +   y 

 def   g  (  z  ): 
     x   =   1    # локальный x затеняет глобальный x 
     return   f  (  z  ) 

 g  (  1  )    # оценивается как 1, а не 2 

Приложения [ править ]

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

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

Первоклассные функции [ править ]

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

;   Верните список всех книг, проданных хотя бы ПОРОГОВЫХ экземпляров. 
  (  определить   (  самых продаваемых книг   порог  ) 
   (  фильтр 
     (  лямбда   (  книга  ) 
       (  >=   (  книга продаж   книг  ))   порог  )) 
     список книг  )) 

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

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

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

// Возвращаем список всех книг, проданных хотя бы «пороговых» копий. 
  функция   bestSellingBooks  (  порог  )   { 
   return   bookList  .   фильтр  (  книга   =>   книга  .  продажи   >=   порог  ); 
  } 

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

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

// Возвращаем функцию, которая аппроксимирует производную f 
 // с использованием интервала dx, который должен быть соответствующим образом малым. 
 функции    производная  (  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   (  лямбда   ( )   секретное сообщение  ))) 

 (  дисплей   (  бар  ) )   ;   печатает «none» 
 (  newline  ) 
 (  foo   «встретимся у доков в полночь»  ) 
 (  display   (  bar  ))   ;   печатает «встретимся в доках в полночь» 
  • Замыкания можно использовать для реализации объектных систем. [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  ; 
  функция   foo  ()   { 
   var   x  ; 
    е   =   функция  ()   {   возвращение   ++  х  ;    }; 
    г   =   функция  ()   {   возвращение   --  х  ;    }; 
    х   =   1  ; 
    alert  (  'внутри foo, вызовите f(): '   +   f  ()); 
  } 
 Фу  ();     // 2 
 оповещения  (  'вызов g(): '   +   g  ());     // 1 (--x) 
 оповещение  (  'вызов g(): '   +   g  ());     // 0 (--x) 
 alert  (  'вызов f(): '   +   f  ());     // 1 (++x) 
 оповещение  (  'вызов f(): '   +   f  ());     // 2 (++x) 

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

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

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

[13]

var   модуль   =   { 
   x  :   42  , 
   getX  :   function  ()   {  верните   это  .   Икс  ;    } 
 } 
 вар   unboundGetX   =   модуль  .   получитьХ  ; 
  консоль  .   журнал  (  unboundGetX  ());    // Функция вызывается в глобальной области 
 // выдает неопределенное значение, поскольку 'x' не указан в глобальной области. 

  варboundGetX    =   unboundGetX  .   привязать  (  модуль  );   закрытия 
  // указываем объектный модуль в качестве консоли  .   журнал  (  boundGetX  ());    // выдает 42 

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

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

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

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

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

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

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

-- Haskell 
 foo   ::   Дробное   a   =>   a   ->   a   ->   (  a   ->   a  ) 
 foo   x   y   =   (  \  z   ->   z   +   r  ) 
           где   r   =   x   /   y 

 f   ::   Дробное   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 подчеркивают разницу:

"Светская беседа" 
 фу 
   |   хз  | 
    хз   :=   #(  1   2   3   4  )  . 
    хз   делаю:  [  :  х   |    ^  х  ]  . 
    ^  0 
 бар 
   Показ расшифровки   :  (  self   foo   printString  )  "печатает 1" 
 ECMAScript 
// Функция   foo  ()   { 
   var   xs   =   [  1  ,   2  ,   3  ,   4  ]; 
    хз  .   forEach  (  функция   (  х  )   {   возврат   х  ;   }); 
    вернуть   0  ; 
  } 
 Оповещение  (  фу  ());    // печатает 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-продолжению пережить тот период, в котором оно может быть успешно вызвано. Учитывать:

«Светская беседа» 
 foo 
     ^  [  :  x   |    ^  х  ] 
  бар 
     |    ж   | 
      f   :=   сам   фу  . 
     f    значение   : 123   «ошибка!» 

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

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

# Ruby 

 # Замыкание с использованием Proc 
 def   foo 
   f   =   Proc  .   new   {   return   «возврат из foo изнутри процедуры»   } 
   f  .   call   # управление оставляет здесь foo 
   return   "return from foo" 
 end 

 # Замыкание с использованием лямбды 
 def   bar 
   f   =   лямбда   {   return   "return from лямбда"   } 
   f  .   call   # управление здесь не покидает бар 
   return   "возврат из бара" 
 end 

 puts   foo   # печатает "возврат из foo изнутри процедуры" 
 puts   bar   # печатает "возврат из бара" 

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

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

;   Схема 
 (  define   call/cc   call-with-current-continuation  ) 

 (  define   (  foo  ) 
   (  call/cc 
    (  лямбда   (  return  ) 
      (  define   (  f  )   (  return   "возврат из foo изнутри proc"  )) 
      (  f  )   ; control оставляет здесь foo 
      (  return   "return from foo"  )))) 

 (  define   (  bar  ) 
   (  call/cc 
    (  лямбда   (  return  ) 
      (  define   (  f  )   (  call/cc   (  лямбда   (  return  )   (  return   " return from лямбда "  ) ))) 
      (  f  )   ; здесь управление не покидает бар 
      (  return   "возврат из бара"  )))) 

 (  display   (  foo  ) )   ;   печатает «возврат из foo изнутри proc» 
 (  новая строка  ) 
 (  display   (  bar  ))   ;   печатает «возврат из бара» 

Замыкающие конструкции [ править ]

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

Обратные вызовы (C) [ править ]

Некоторые библиотеки 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  );    // тип функции int->int 

 fn_int_to_int   adder  (  int   number  )   { 
   int   add   (  int   value  )   {   возвращаемое   значение   +   число  ;    } 
   Вернуться   и  добавить  ;    // Оператор & здесь необязателен, поскольку имя функции в C — это указатель, указывающий на саму себя 
 } 

 int   main  (  void  )   { 
   fn_int_to_int   add10   =   adder  (  10  ); 
    printf  (  "%d  \n  "  ,   add10  (  1  )); 
    вернуть   0  ; 
  } 

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

#include   <stdio.h> 

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

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

Локальные классы и лямбда-функции (Java) [ править ]

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

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

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

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

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

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

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

public   class   EnclosingClass   { 
     /* Определить внутренний класс */ 
     public   class   InnerClass   { 
         public   int   ignoreAndReturnCounter  ()   { 
             return   counter  ++  ; 
          } 
     } 

     частный   int   счетчик  ; 
      { 
         счетчик   =   0  ; 
      } 

     Public   int   getCounter  ()   { 
         возврат   счетчика  ; 
      } 

     Public   static   void   main  (  String  []   args  )   { 
         EnclosingClass   enclosingClassInstance   =   новый   EnclosingClass  (); 
          /* Создать экземпляр внутреннего класса с привязкой к экземпляру */ 
         EnclosingClass  .   InnerClass   InternalClassInstance   = 
             EnclosingClassInstance  .   новый   ВнутреннийКласс  (); 

          for   (  int   i   =   enclosingClassInstance  .  getCounter  (); 
              (  i   =   InternalClassInstance  .  инкрементАнкретурнкаунтер  ())   <   10  ; 
              /* шаг приращения опущен */  )   { 
             System  .   вне  .   печатьln  (  я  ); 
          } 
     } 
 } 

При выполнении будут выведены целые числа от 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  .   длина  (); 

      Система  .   вне  .   println  (   length  .  apply  (  "Привет, мир!"  )   );    // Напечатаем 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  --  ; 
	   }   копировать  ]   автовыпуск  ]; 
  } 

 IntBlock   f   =   downCounter  (  5  ); 
  NSLog  (  @"%d"  ,   f  ()); 
  NSLog  (  @"%d"  ,   f  ()); 
  NSLog  (  @"%d"  ,   f  ()); 

Делегаты (C#, VB.NET, D) [ править ]

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

вар   данные   =   новый  []   {  1  ,   2  ,   3  ,   4  }; 
  вар   множитель   =   2  ; 
  вар   результат   =   данные  .   Выберите  (  x   =>   x   *   множитель  ); 

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

Тусклые   данные   =   {  1  ,   2  ,   3  ,   4  } 
 Тусклый   множитель   =   2 
 Тусклый   результат   =   данные  .   Выберите  (  Функция  (  x  )   x   *   множитель  ) 

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

автоматический   тест1  ()   { 
     int   a   =   7  ; 
      вернуть   делегата  )   {   вернуть   +   3   ;  (    };    // конструкция анонимного делегата 
 } 

 auto   test2  ()   { 
     int   a   =   20  ; 
      int   foo  ()   {   return   a   +   5  ;    }   // внутренняя функция 
     return   &  foo  ;     // другой способ создания делегата 
 } 

 void   bar  ()   { 
     auto   dg   =   test1  (); 
      дг  ();       // =10 // ок, test1.a находится в замыкании и все еще существует 

     dg   =   test2  (); 
      дг  ();       // =25 // ок, test2.a закрыт и все еще существует 
 } 

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

Встроенные агенты (Eiffel) [ править ]

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

ок_кнопка  .   клик_событие  .   подписаться   ( 
	 агент   (  x  ,   y  :   INTEGER  )   сделать 
		 карту  .  Country_at_coordinates   (  x  ,   y  ).  Показать 
	 конец 
 ) 

аргумент в пользу 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
Номер скриншота №: 1EFC36FC324F6096D4689B94EAF55DCE__1715617080
URL1:https://en.wikipedia.org/wiki/Closure_(computer_science)
Заголовок, (Title) документа по адресу, URL1:
Closure (computer programming) - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть, любые претензии не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, денежную единицу можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)