Почему программа использует закрытие?


58

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

Примерами замыканий в Swift являются обращения к NSUrl и использование обратного геокодера. Вот один из таких примеров. К сожалению, эти курсы просто представляют собой закрытие; они не объясняют, почему решение кода написано как замыкание.

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


7
«Недавно рассмотренное объяснение Closures здесь» - вам не хватает ссылки?
Дан Пичельман

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

5
Просто лакомый кусочек: критической точкой замыканий является лексическая область видимости (в отличие от динамической области видимости), замыкания без лексической области видимости как бы бесполезны. Закрытия иногда называют Лексическими Закрытиями.
Хоффман

1
Замыкания позволяют создавать экземпляры функций .
user253751

1
Прочитайте любую хорошую книгу по функциональному программированию. Возможно, начнем с SICP
Василий Старынкевич

Ответы:


33

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

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

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

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

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

Вы можете написать это без замыкания, но это потребует намного больше кода, и будет сложнее следовать. Кроме того, JavaScript имеет довольно многословный лямбда-синтаксис. Например, в Scala все тело функции будет выглядеть так:

bookList filter (_.sales >= threshold)

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

const bestSellingBooks = (threshold) => bookList.filter(book => book.sales >= threshold);

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


Как именно крышки уменьшают сцепление?
сбиченко

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

51

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

Допустим, вы хотите отобразить массив в виде таблицы HTML. Вы можете сделать это так:

function renderArrayAsHtmlTable (array) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + object + "</td></tr>";
  }
  table += "</table>";
  return table;
}

Но вы во власти JavaScript, как каждый элемент в массиве будет отображаться. Если вы хотите контролировать рендеринг, вы можете сделать это:

function renderArrayAsHtmlTable (array, renderer) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + renderer(object) + "</td></tr>";
  }
  table += "</table>";
  return table;
}

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

Что если вы хотите отобразить промежуточную сумму в каждой строке таблицы? Вам понадобится переменная для отслеживания этой суммы, не так ли? Замыкание позволяет вам написать функцию рендерера, которая закрывает переменную промежуточного итога, и позволяет вам написать рендерер, который может отслеживать итоговую сумму:

function intTableWithTotals (intArray) {
  var total = 0;
  var renderInt = function (i) {
    total += i;
    return "Int: " + i + ", running total: " + total;
  };
  return renderObjectsInTable(intArray, renderInt);
}

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

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

Дальнейшее чтение


10
В общем, вы можете сказать, что «первоклассная функция == объект только с одним методом», «Closure == объект только с одним методом и состоянием», «object == связка замыканий».
Йорг Миттаг

Выглядит неплохо. Еще одна вещь, и это может быть педантизмом, заключается в том, что Javascript является объектно-ориентированным, и вы можете создать объект, содержащий переменную total, и передать его. Замыкания остаются гораздо более мощными, чистыми и элегантными, а также создают гораздо более идиоматический Javascript, но способ написания может означать, что Javascript не может сделать это объектно-ориентированным способом.
KRyan

2
На самом деле, чтобы быть действительно педантичным: замыкания - это то, что делает JavaScript объектно-ориентированным в первую очередь! ОО - это абстракция данных, а замыкания - это способ абстрагирования данных в JavaScript.
Йорг Миттаг

@ JörgWMittag: Подобно тому, как вы можете видеть cloures как объекты только с одним методом, вы можете видеть объекты как совокупность замыканий, которые закрываются по одним и тем же переменным: переменные-члены объекта - это просто локальные переменные конструктора объекта и методы объекта являются замыканиями, определенными в области видимости конструктора и доступными для последующего вызова. Функция конструктора возвращает функцию более высокого порядка (объект), которая может отправлять каждое закрытие в соответствии с именем метода, который используется для вызова.
Джорджио

@ Джорджио: Действительно, именно так объекты, как правило, реализуются в Scheme, например, и фактически также, как объекты реализуются в JavaScript (неудивительно, учитывая его тесную связь со Scheme, в конце концов, Брендан Айх был первоначально нанят для разработки Схема набирает диалект и реализует встроенный интерпретатор Scheme внутри Netscape Navigator, и только позднее ему было приказано создать язык «с объектами, похожими на C ++», после чего он внес минимально минимальное количество изменений, чтобы соответствовать этим маркетинговым требованиям).
Jörg W Mittag

23

Цель closuresсостоит в том, чтобы просто сохранить государство; отсюда и название closure- оно закрывается над государством. Для простоты дальнейшего объяснения я буду использовать Javascript.

Обычно у вас есть функция

function sayHello(){
    var txt="Hello";
    return txt;
}

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

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

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

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

  • принимает одну или несколько функций в качестве входа
  • выводит функцию

Простой, но, правда, не слишком полезный пример:

 makeadder=function(a){
     return function(b){
         return a+b;
     }
 }

 add5=makeadder(5);
 console.log(add5(10)); 

Вы определяете функцию makedadder, которая принимает один параметр в качестве входных данных и возвращает функцию . Существует внешняя функция function(a){}и внутренняя. function(b){}{} Кроме того, вы определяете (неявно) другую функцию add5в результате вызова функции более высокого порядка makeadder. makeadder(5)возвращает анонимную ( внутреннюю ) функцию, которая, в свою очередь, принимает 1 параметр и возвращает сумму параметра внешней функции и параметра внутренней функции.

Трик в том, что при возвращении на внутреннюю функцию, которая делает фактическое добавление, объем параметра внешней функции ( a) сохраняются. add5 помнит , что параметр aбыл 5.

Или показать хотя бы один полезный пример:

  makeTag=function(openTag, closeTag){
     return function(content){
         return openTag +content +closeTag;
     }
 }

 table=makeTag("<table>","</table>")
 tr=makeTag("<tr>", "</tr>");
 td=makeTag("<td>","</td>");
 console.log(table(tr(td("I am a Row"))));

Другим распространенным случаем использования является так называемое выражение вызываемой функции IIFE =. В javascript очень распространено подделывать закрытые переменные-члены. Это делается с помощью функции, которая создает частную область видимости = closure, потому что она сразу после вызова определения вызывается. Структура есть function(){}(). Обратите внимание на скобки ()после определения. Это позволяет использовать его для создания объекта с раскрытием шаблона модуля . Хитрость заключается в создании области действия и возвращении объекта, который имеет доступ к этой области после выполнения IIFE.

Пример Адди выглядит так:

 var myRevealingModule = (function () {

         var privateVar = "Ben Cherry",
             publicVar = "Hey there!";

         function privateFunction() {
             console.log( "Name:" + privateVar );
         }

         function publicSetName( strName ) {
             privateVar = strName;
         }

         function publicGetName() {
             privateFunction();
         }


         // Reveal public pointers to
         // private functions and properties

         return {
             setName: publicSetName,
             greeting: publicVar,
             getName: publicGetName
         };

     })();

 myRevealingModule.setName( "Paul Kinlan" );

Возвращаемый объект имеет ссылки на функции (например publicSetName), которые в свою очередь имеют доступ к «закрытым» переменным privateVar.

Но это более особые случаи использования Javascript.

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

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


«Цель замыканий - просто сохранить состояние; отсюда и имя замыкания - оно закрывается над состоянием». Это действительно зависит от языка. Закрытия закрываются по внешним именам / переменным. Они могут обозначать состояние (ячейки памяти, которые могут быть изменены), но могут также обозначать значения (неизменяемые). Замыкания в Haskell не сохраняют состояние: они сохраняют информацию, которая была известна на данный момент и в контексте, в котором было создано замыкание.
Джорджио

1
»Они сохраняют информацию, которая была известна в данный момент и в контексте, в котором было создано закрытие« независимо от того, может ли оно быть изменено или нет, это состояние - возможно, неизменное состояние .
Томас Джанк

16

Существует два основных варианта использования замыканий:

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

  2. Callbacks. Они также известны как «делегаты» или «обработчики событий» в зависимости от языка и платформы. Идея состоит в том, что у вас есть настраиваемый объект, который в определенных четко определенных точках будет выполнять событие , которое запускает замыкание, передаваемое кодом, который его устанавливает. Например, в пользовательском интерфейсе вашей программы у вас может быть кнопка, и вы даете ей закрытие, содержащее код, который будет выполнен, когда пользователь нажимает на кнопку.

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


23
Итак, в основном обратные вызовы, так как первый пример также является обратным вызовом.
Роберт Харви

2
@RobertHarvey: Технически это правда, но это разные ментальные модели. Например, (вообще говоря) вы ожидаете, что обработчик событий будет вызываться несколько раз, но ваше асинхронное продолжение будет вызываться только один раз. Но да, технически все, что вы делаете с закрытием, является обратным вызовом. (Если вы не конвертируете его в дерево выражений, но это совсем другое дело.);)
Мейсон Уилер

4
@RobertHarvey: просмотр замыканий в виде обратных вызовов создает у вас настроение, которое действительно мешает вам эффективно их использовать. Закрытия являются обратными вызовами на стероидах.
gnasher729

13
@MasonWheeler Скажи так, это звучит неправильно. Обратные вызовы не имеют ничего общего с замыканиями; конечно, вы можете вызвать функцию внутри замыкания или вызвать замыкание в качестве обратного вызова; но обратный вызов не обязательно закрытие. Обратный вызов - это простой вызов функции. Обработчик событий сам по себе не является закрытием. Делегат не является обязательным закрытием: это прежде всего C # -путь указателей на функции. Конечно, вы можете использовать его для создания замыкания. Ваше объяснение неточно. Дело в том, чтобы закрыть состояние и использовать его.
Томас Джанк

5
Возможно, здесь происходят специфичные для JS коннотации, которые заставляют меня неправильно понимать, но этот ответ звучит для меня совершенно неправильно. Асинхронность или обратные вызовы могут использовать все, что можно вызвать. Закрытие не требуется ни для одного из них - обычная функция подойдет. Между тем замыкание, как определено в функциональном программировании или используется в Python, полезно, как говорит Томас, потому что оно «закрывается» над некоторым состоянием, то есть переменной, и дает функции во внутренней области согласованный доступ к этой переменной. значение, даже если функция вызывается и выходит много раз.
Джонатан Хартли

13

Пара других примеров:

Сортировка
Большинство функций сортировки работают путем сравнения пар объектов. Необходима некоторая техника сравнения. Ограничение сравнения конкретным оператором означает довольно негибкий вид. Гораздо лучший подход - получить функцию сравнения в качестве аргумента функции сортировки. Иногда функция сравнения без сохранения состояния работает нормально (например, сортировка списка чисел или имен), но что, если для сравнения требуется состояние?

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

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


Случайные числа
В оригинале rand()не было аргументов. Генераторы псевдослучайных чисел нуждаются в состоянии. Некоторым (например, Mersenne Twister) нужно много государства. Даже простое, но ужасно rand()необходимое состояние. Прочитайте статью из математического журнала о новом генераторе случайных чисел, и вы неизбежно увидите глобальные переменные. Это приятно для разработчиков техники, не так приятно для звонящих. Инкапсуляция этого состояния в структуре и передача структуры в генератор случайных чисел является одним из способов решения глобальной проблемы с данными. Этот подход используется во многих неOO-языках для повторного ввода генератора случайных чисел. Закрытие скрывает это состояние от вызывающей стороны. Замыкание предлагает простую последовательность вызова rand()и вход в инкапсулированное состояние.

Там больше случайных чисел, чем просто PRNG. Большинство людей, которые хотят случайности, хотят, чтобы она распространялась определенным образом. Я начну с чисел, произвольно взятых от 0 до 1, или U (0,1) для краткости. Подойдет любой PRNG, который генерирует целые числа от 0 до некоторого максимума; просто разделите (в виде числа с плавающей запятой) случайное целое число на максимум. Удобный и универсальный способ реализовать это - создать замыкание, которое принимает замыкание (PRNG) и максимальное значение в качестве входных данных. Теперь у нас есть общий и простой в использовании генератор случайных чисел для U (0,1).

Существует ряд других распределений, кроме U (0,1). Например, нормальное распределение с определенным средним и стандартным отклонением. Каждый алгоритм генерации нормального распределения, с которым я столкнулся, использует генератор U (0,1). Удобный и общий способ создания нормального генератора - это создание замыкания, которое инкапсулирует генератор U (0,1), среднее значение и стандартное отклонение в качестве состояния. Это, по крайней мере, концептуально, замыкание, которое принимает замыкание, которое принимает замыкание в качестве аргумента.


7

Замыкания эквивалентны объектам, реализующим метод run (), и, наоборот, объекты можно эмулировать с замыканиями.

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

  • Преимущество объектов заключается в возможности более сложных взаимодействий: нескольких методов и / или различных интерфейсов.

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

 (let ((seen))
    (defun register-name (name)
       (pushnew name seen :test #'string=))

    (defun all-names ()
       (copy-seq seen))

    (defun reset-name-registry ()
       (setf seen nil)))

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


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

Тем не менее, довольно просто предотвратить такое неправильное использование путем захвата управляющей переменной, которая будет защищать выполнение замыкания. Точнее, вот что я имею в виду (в Common Lisp):

(defun guarded (function)
  (let ((active t))
    (values (lambda (&rest args)
              (when active
                (apply function args)))
            (lambda ()
              (setf active nil)))))

Здесь мы берем обозначение функции functionи возвращаем два замыкания, каждое из которых захватывает локальную переменную с именем active:

  • первый делегирует function, только когда activeэто правда
  • второй наборы actionк nil, ака false.

Вместо этого (when active ...), конечно, возможно иметь (assert active)выражение, которое может генерировать исключение в случае, если замыкание вызывается, когда оно не должно быть. Кроме того, имейте в виду, что небезопасный код уже может выдать исключение сам по себе при плохом использовании, поэтому вам редко нужна такая оболочка.

Вот как вы бы это использовали:

(use-package :metabang-bind) ;; for bind

(defun example (obj1 obj2)
  (bind (((:values f f-deactivator)(guarded (lambda () (do-stuff obj1))))
         ((:values g g-deactivator)(guarded (lambda () (do-thing obj2)))))

    ;; ensure the closure are inactive when we exit
    (unwind-protect
         ;; pass closures to other functions
         (progn
           (do-work f)
           (do-work g))

      ;; cleanup code: deactivate closures
      (funcall f-deactivator)
      (funcall g-deactivator))))

Обратите внимание, что дезактивирующие замыкания могут также передаваться и другим функциям; здесь локальные activeпеременные не разделяются между fи g; Кроме того , в дополнение к active, fотносится только к obj1и gотносится только к obj2.

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


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

@supercat: взгляните на Objective-C и Swift.
gnasher729

1
@supercat Не будет ли такой же недостаток с объектами с состоянием?
Андрес Ф.

@AndresF .: можно заключить объект с состоянием в оболочку, которая поддерживает аннулирование; если код инкапсулирует частное владение List<T>в (гипотетическом классе) TemporaryMutableListWrapper<T>и предоставляет его внешнему коду, можно гарантировать, что, если он аннулирует оболочку, внешний код больше не будет иметь никакого способа манипулировать List<T>. Можно спроектировать замыкания так, чтобы они стали недействительными, когда они достигли ожидаемой цели, но это вряд ли удобно. Закрытия существуют для того, чтобы сделать определенные шаблоны удобными, и усилия, необходимые для их защиты, сведут на нет это.
суперкат

1
@coredump: В C #, если у двух замыканий есть какие-либо общие переменные, один и тот же объект, сгенерированный компилятором, будет служить обоим, поскольку изменения, внесенные в совместно используемую переменную одним замыканием, должны просматриваться другим. Чтобы избежать такого совместного использования, требовалось бы, чтобы каждое замыкание было своим собственным объектом, который содержит свои собственные неразделенные переменные, а также ссылкой на общий объект, который содержит общие переменные. Не невозможно, но это добавило бы дополнительный уровень разыменования для большинства переменных обращений, замедляя все.
Суперкат

6

Ничего, что еще не было сказано, но, возможно, более простой пример.

Вот пример JavaScript с использованием тайм-аутов:

// Example function that logs something to the browser's console after a given delay
function delayedLog(message, delay) {
  // this function will be called when the timer runs out
  var fire = function () {
    console.log(message); // closure magic!
  };

  // set a timeout that'll call fire() after a delay
  setTimeout(fire, delay);
}

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

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

Но давайте представим, что в JavaScript нет замыканий.

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

Другим способом было бы поместить messageпеременную в другую область, которая будет доступна после того, delayedLog()как область исчезнет.

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

Вы можете создать экземпляр объекта тайм-аута, который «группирует» таймер с сообщением. Контекст объекта - это более или менее область видимости. Тогда вы бы запустили таймер в контексте объекта, чтобы у него был доступ к нужному сообщению. Но вам придется хранить этот объект, потому что без каких-либо ссылок он будет собирать мусор (без замыканий также не будет никаких неявных ссылок на него). И вам придется удалить объект, как только у него истечет время ожидания, в противном случае он просто останется. Таким образом, вам понадобится какой-то список объектов тайм-аута и периодически проверяйте его на предмет «потраченных» объектов для удаления - или объекты будут добавлять и удалять себя из списка, и

Так что ... да, это становится скучно.

К счастью, вам не нужно использовать более широкую область видимости или оборачивать объекты только для того, чтобы сохранить определенные переменные. Поскольку JavaScript имеет замыкания, у вас уже есть именно тот объем, который вам нужен. Область, которая дает вам доступ к messageпеременной, когда вам это нужно. И из-за этого вы можете сойти с рук, delayedLog()как написано выше.


У меня есть проблема, называя это закрытием . Возможно, я бы назвал это случайным закрытием . messageзаключен в область действия функции fireи поэтому упоминается в дальнейших вызовах; но это делает это случайно, так сказать. Технически это закрытие. +1 в любом случае;)
Томас Джанк

@ThomasJunk Я не уверен, что полностью следую. Как будет выглядеть « не случайное закрытие»? Я видел ваш makeadderпример выше, который, на мой взгляд, выглядит примерно так же. Вы возвращаете «карри» функцию, которая принимает один аргумент вместо двух; используя те же средства, я создаю функцию, которая принимает нулевые аргументы. Я просто не возвращаю его, а передаю setTimeout.
Фламбино

«Я просто не отвечаю», возможно, в этом и заключается смысл для меня. Я не могу четко сформулировать свою «озабоченность»;) В техническом плане вы на 100% правы. Ссылка messageв fireгенерирует замыкание. И когда он вызывается, setTimeoutон использует сохраненное состояние.
Томас Джанк

1
@ThomasJunk Я вижу, как это может пахнуть немного по-другому. Я, возможно, не разделяю вашу озабоченность, хотя :) Или, во всяком случае, я бы не назвал это «случайным» - почти уверен, что я специально его так закодировал;)
Flambino

3

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

protected function registerRoutes($dic)
{
  $router = $dic['router'];

  $router->map(['GET','OPTIONS'],'/api/users',function($request,$response) use ($dic)
  {
    $controller = $dic['user_api_controller'];
    return $controller->findAllAction($request,$response);
  })->setName('api_users');
}

В общем, я регистрирую функцию, которая будет выполняться для URI / api / users . Это на самом деле функция промежуточного программного обеспечения, которая в конечном итоге хранится в стеке. Другие функции будут обернуты вокруг него. Совсем как Node.js / Express.js .

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


-1

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

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

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

У вас есть функция, в которой необходимо выполнять некоторый нетривиальный код в разных ситуациях. Используется для создания дублирования кода или искаженного кода, так что нетривиальный код будет присутствовать только один раз. Trivial: Вы назначаете замыкание переменной и вызываете его наиболее очевидным образом, где бы оно ни было необходимо.

Многопоточность: iOS / MacOS X имеет такие функции, как «выполнить это замыкание в фоновом потоке», «... в основном потоке», «... в основном потоке, через 10 секунд». Это делает многопоточность тривиальной .

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

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


-4

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

Выше приведено краткое изложение «Понимание лямбда-выражений» Дэна Авидара.

Это прояснило использование замыканий для меня, потому что оно проясняет альтернативы (замыкание и метод) и преимущества каждого.

Следующий код используется один раз и только один раз во время настройки. Запись его на место под viewDidLoad избавляет от необходимости искать его в другом месте и сокращает размер кода.

myPhoton!.getVariable("Temp", completion: { (result:AnyObject!, error:NSError!) -> Void in
  if let e = error {
    self.getTempLabel.text = "Failed reading temp"
  } else {
    if let res = result as? Float {
    self.getTempLabel.text = "Temperature is \(res) degrees"
    }
  }
})

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

Еще одно закрытие; этот захватывает значение ...

let animals = ["fish", "cat", "chicken", "dog"]
let sortedStrings = animals.sorted({ (one: String, two: String) -> Bool in return one > two
}) println(sortedStrings)

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

Пока вы голосуете за этот ответ, прочитайте это ... Когда я должен голосовать? Используйте свои отрицательные отзывы всякий раз, когда вы сталкиваетесь с вопиюще небрежным постом без усилий или ответом, который явно и, возможно, опасно неверен. В моем ответе могут отсутствовать некоторые причины использования замыкания, но он не является ни вопиюще неряшливым, ни опасно неправильным. Есть много работ и обсуждений замыканий, которые сосредотачиваются на функциональном программировании и лямбда-нотации. Весь мой ответ говорит, что это объяснение функционального программирования помогло мне понять замыкания. Это и постоянная ценность.
Бендрикс

1
Ответ, возможно, не опасен , но, безусловно, «явно […] неверен». В нем используются неправильные термины для концепций, которые в значительной степени дополняют друг друга и поэтому часто используются вместе - именно там, где ясность разграничения необходима. Это может вызвать проблемы проектирования для программистов, читающих это, если оставить их без внимания. (И поскольку, насколько я могу судить, пример кода просто не содержит никаких замыканий, только лямбда-функцию, это не помогает объяснить, почему замыкания были бы полезны.)
Натан Тагги

Натан, этот пример кода является закрытием в Swift. Вы можете спросить кого-то еще, если вы сомневаетесь во мне. Итак, если это закрытие, вы бы проголосовали?
Бендрикс

1
Apple довольно четко описывает, что именно они подразумевают под закрытием в своей документации . «Замыкания - это автономные блоки функциональности, которые можно передавать и использовать в вашем коде. Замыкания в Swift аналогичны блокам в C и Objective-C и лямбдам в других языках программирования». Когда вы переходите к реализации и терминологии, это может сбивать с толку .
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.