Чому програма використовує закриття?


58

Прочитавши багато публікацій, де пояснюються питання про закриття, я все ще пропускаю ключове поняття: Чому писати про закриття? Яке конкретно завдання виконає програміст, який може бути найкращим чином виконаний при закритті?

Прикладами закриттів у Swift є доступ до NSUrl та використання зворотного геокодера. Ось один із таких прикладів. На жаль, ці курси просто представлені на закритті; вони не пояснюють, чому рішення коду пишеться як закриття.

Приклад реальної проблеми програмування у світі, який може змусити мій мозок сказати: "ага, я мушу написати це завершення", був би більш інформативним, ніж теоретична дискусія. На цьому веб-сайті немає дефіциту теоретичних дискусій.


7
"Нещодавно переглянута декларація про закриття тут" - пропустите посилання?
Ден Пішельман

2
Ви повинні розмістити роз'яснення щодо асинхронного завдання в коментарі, нижче відповідної відповіді. Це не є частиною вашого початкового запитання, і відповідь ніколи не отримає повідомлення про вашу редакцію.
Роберт Харві

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

1
Закриття дозволяє створювати функції .
користувач253751

1
Прочитайте будь-яку хорошу книгу про функціональне програмування. Можливо, почніть з SICP
Basile Starynkevitch

Відповіді:


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);

У власному коді шукайте місця, де ви генеруєте велику кількість котлів просто для передачі тимчасових значень з одного місця в інше. Це відмінні можливості розглянути можливість заміни закриття.


Як саме закриття зменшує з'єднання?
sbichenko

Без закриттів вам потрібно накладати обмеження як на 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, ви можете написати клас, який містить цю загальну змінну, і передати це навколо, а не створювати закриття. Але закриття - це набагато більш потужний, чистий та елегантний спосіб зробити це.

Подальше читання


10
Загалом, ви можете сказати, що "функція першого класу == об'єкт лише одним методом", "закриття == об'єкт лише одним методом і станом", "об'єкт == пучок закриттів".
Йорг W Міттаг

Виглядає добре. Інша справа, і це може бути педантизм, що Javascript є об'єктно-орієнтованим, і ви могли б створити об'єкт , який містить загальну змінну і передати навколо. Закриття залишаються набагато більш потужними, чистими та витонченими, а також створюють набагато ідіоматичніший Javascript, але спосіб написання може означати, що Javascript не може робити це об'єктно-орієнтованим способом.
KRyan

2
Власне, бути по- справжньому педантичним: закриття - це те, що робить насамперед об’єктно-орієнтованим JavaScript! OO - це абстракція даних, а закриття - спосіб здійснення абстрагування даних у JavaScript.
Йорг W Міттаг

@ JörgWMittag: Як і ви можете бачити закриття як об'єкти лише одним методом, ви можете бачити об'єкти як колекцію закриттів, що закриваються над одними і тими ж змінними: змінні-члени об'єкта - це лише локальні змінні конструктора об'єкта та методи об'єкта - це замикання, визначені всередині об'єкта конструктора та доступні для виклику пізніше. Функція конструктора повертає функцію вищого порядку (об’єкт), яка може відправляти на кожне закриття відповідно до імені методу, який використовується для виклику.
Джорджіо

@Giorgio: Дійсно, саме так об'єкти реалізуються, наприклад, у Scheme, і насправді це також, як об’єкти реалізуються в JavaScript (що не дивно, враховуючи його тісний зв’язок із Scheme, зрештою, Брендана Ейха спочатку найняли для створення дизайну Діалект схеми та реалізуйте вбудований інтерпретатор схеми всередині Netscape Navigator, і лише пізніше було наказано скласти мову "з об'єктами, схожими на C ++", після чого він вніс абсолютну мінімальну кількість змін, щоб відповідати цим вимогам маркетингу).
Йорг W Міттаг

22

Мета 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: просто необхідно покладатися на закриття, щоб обійти деякі примхи мови.


"Мета закриття - просто зберегти державу; звідси назва" закриття - вона закривається над державою ".": Це дійсно залежить від мови. Замикання закриваються над зовнішніми назвами / змінними. Вони можуть позначати стан (місця пам'яті, які можна змінити), але можуть також позначати значення (незмінні). Закриття в Хаскеллі не зберігає стан: вони зберігають інформацію, яка була відома на даний момент і в контексті, в якому було створено закриття.
Джорджіо

1
»Вони зберігають інформацію, яка була відома на даний момент і в контексті, в якому було створено закриття,« незалежно від того, може бути змінено чи ні, це стан - можливо, незмінний стан .
Томас Джунк

16

Є два основні випадки використання для закриття:

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

  2. Відкликання дзвінків Вони також відомі як "делегати" або "обробники подій" залежно від мови та платформи. Ідея полягає в тому, що у вас є об'єкт, що настроюється, який у певні чітко визначені моменти буде виконувати подію , яка виконує закриття, передане кодом, який її встановлює. Наприклад, в інтерфейсі вашої програми у вас може бути кнопка, і ви даєте їй закриття, яке містить код, який повинен виконуватися, коли користувач натискає кнопку.

Є кілька інших застосувань для закриття, але це два основних.


23
Отже, в основному зворотні дзвінки тоді, оскільки перший приклад - це також зворотний виклик.
Роберт Харві

2
@RobertHarvey: Технічно правда, але вони різні ментальні моделі. Наприклад, (загалом кажучи,) ви очікуєте, що обробник подій буде викликаний кілька разів, але ваше продовження асинхронізації буде викликано лише один раз. Але так, технічно все, що ви робите із закриттям, - це зворотній дзвінок. (Якщо ви не перетворите це на дерево виразів, але це зовсім інша справа.);)
Мейсон Уілер

4
@RobertHarvey: Перегляд закриття під час зворотних дзвінків ставить вас до думки, що дійсно не дозволить вам ефективно їх використовувати. Закриття - зворотний виклик стероїдів.
gnasher729

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

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

13

Кілька інших прикладів:

Сортування
Більшість функцій сортування діють шляхом порівняння пар об'єктів. Потрібна певна техніка порівняння. Обмеження порівняння певним оператором означає досить негнучку. Набагато кращим підходом є отримання функції порівняння як аргументу функції сортування. Іноді функція порівняння без стану працює непогано (наприклад, сортування списку чисел чи імен), але що робити, якщо для порівняння потрібно стан?

Наприклад, розглянемо сортування списку міст за відстані до певного місця. Неприємним рішенням є зберігання координат цього місця в глобальній змінній. Це робить функцію порівняння самою без громадянства, але ціною глобальної змінної.

Цей підхід виключає наявність декількох потоків одночасно сортування одного і того ж списку міст за їх відстані до двох різних місць. Закриття, яке закриває місцеположення, вирішує цю проблему, і вона позбавляється від необхідності глобальної змінної.


Випадкові числа
Оригінал rand()не брав аргументів. Генератори псевдовипадкових чисел потребують стану. Деяким (наприклад, Mersenne Twister) потрібно багато штату. Навіть проста, але страшна rand()потрібна держава. Прочитайте журнал з математичного журналу на новому генераторі випадкових чисел, і ви неминуче побачите глобальні змінні. Це приємно для розробників техніки, не так приємно для абонентів. Інкапсуляція цього стану в структуру та передача структури генератору випадкових чисел є одним із способів вирішення глобальної проблеми даних. Це підхід, який застосовується у багатьох мовах, що не входять в ООС, до створення генератора випадкових чисел. Закриття приховує цей стан від абонента. Замикання пропонує просту послідовність виклику 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.

Інший момент, про який згадує supercat, полягає в тому, що закриття може призвести до витоку пам’яті, але, на жаль, це стосується майже всього в місцях, де збираються сміття. Якщо вони доступні, це можна вирішити слабкими покажчиками (саме закриття може зберігатися в пам'яті, але не перешкоджає збору сміття інших ресурсів).


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

@supercat: Погляньте на Objective-C та Swift.
gnasher729

1
@supercat Чи не був би такий самий недолік у стаціонарних об'єктів?
Андрес Ф.

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

1
@coredump: У C #, якщо два закриття мають загальні змінні, той самий об’єкт, що генерується компілятором, буде обслуговувати і те, і інше, оскільки зміни, внесені до спільної змінної одним закриттям, повинні бачити інша. Уникнення цього спільного використання вимагало б, щоб кожне закриття було власним об'єктом, який містить власні нерозділені змінні, а також посилання на спільний об'єкт, який містить спільні змінні. Це неможливо, але це додало б додатковий рівень перенаправлення до більшості змінних підходів, уповільнюючи все.
supercat

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приклад вище, який, на мій погляд, виглядає приблизно однаково. Ви повертаєте функцію "curried", яка займає один аргумент замість двох; використовуючи ті ж засоби, я створюю функцію, яка бере нульові аргументи. Я просто не повертаю його, а передаю setTimeoutнатомість.
Фламбіно

"Я просто не повертаю його", можливо, саме в цьому полягає зміна. Я не можу зовсім сформулювати свою «стурбованість»;) У технічному плані ви на 100% праві. Посилання messageв fireпороджує закриття. І коли він називається, setTimeoutвін використовує збережений стан.
Томас Юнк

1
@ThomasJunk Я бачу, як це може трохи пахнути. Я, можливо, не поділяю вашу стурбованість :) Або, в будь-якому випадку, я б не назвав це "випадковим" - майже впевнений, що я його закодував таким чином спеціально;)
Фламбіно

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');
}

Таким чином, я в основному реєструю функцію, яка буде виконуватися для / api / користувачів URI . Це насправді функція середнього програмного забезпечення, яка в кінцевому підсумку зберігається в стеці. Інші функції будуть обгорнуті навколо нього. Настільки ж, як і Node.js / Express.js .

Ін'єкції залежностей контейнера можна ( з допомогою пункту використання) всередині функції , коли вона викликається. Можна зробити якийсь клас дії маршруту, але цей код виявляється простішим, швидшим та легшим у обслуговуванні.


-1

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

Тривіальний приклад - хороший старий qsort: це функція сортування даних. Ви повинні дати йому вказівник на функцію, яка порівнює два об'єкти. Отже, ви повинні написати функцію. Цю функцію може знадобитися параметризувати, це означає, що ви надаєте їй статичні змінні. Це означає, що це не безпечно для потоків. Ви в ДС. Отже, ви пишете альтернативу, яка займає закриття замість вказівника функції. Ви моментально вирішуєте проблему параметризації, оскільки параметри стають частиною закриття. Ви зробите свій код більш читаним, оскільки ви записуєте, як об'єкти порівнюються безпосередньо з кодом, який викликає функцію сортування.

Існує безліч ситуацій, коли потрібно виконати певну дію, яка вимагає великої кількості коду плит котла, плюс один крихітний, але важливий фрагмент коду, який потрібно адаптувати. Ви уникаєте кодового коду, записуючи один раз функцію, яка приймає параметр закриття та виконує весь код котла, а потім ви можете викликати цю функцію та передати код, який буде адаптований як закриття. Дуже компактний і читаний спосіб написання коду.

У вас є функція, коли деякий нетривіальний код потрібно виконувати в різних ситуаціях. Це використовувалося для створення дублювання коду або перекрученого коду, щоб нетривіальний код був присутній лише один раз. 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
На жаль, це бентежить закриття та лямбда. Лямбди часто використовують закриття, оскільки вони, як правило, набагато корисніші, якщо їх визначено a) дуже коротко і b) всередині та залежно від конкретного контексту методу, включаючи змінні. Однак фактичне закриття не має нічого спільного з ідеєю лямбда, яка, в основному, полягає у здатності визначити першокласну функцію на місці та передати її навколо для подальшого використання.
Натан Туггі

Поки ви голосуєте за цю відповідь, прочитайте цю ... Коли я повинен проголосувати? Використовуйте свої голоси, коли ви стикаєтеся з надзвичайно неохайною публікацією, яка не витрачає зусиль, або відповіді, яка явно і може бути небезпечно неправильною. Моя відповідь може не мати деяких причин використання закриття, але це не є дуже грубим, ані небезпечно неправильним. Існує багато робіт і обговорень закриттів, які зосереджені на функціональному програмуванні та лямбда-нотації. У моїй відповіді сказано, що це пояснення функціонального програмування допомогло мені зрозуміти закриття. Це і стійке значення.
Бендрікс

1
Відповідь, можливо, не є небезпечною , але, безумовно, "явно [...] невірна". Він використовує неправильні терміни для понять, які сильно доповнюють один одного, і тому часто використовуються разом - саме там, де чіткість розрізнення є важливою. Це, ймовірно, спричинить проблеми із дизайном для програмістів, які читають це, якщо залишити його без адреси. (Оскільки, наскільки я можу сказати, приклад коду просто не містить закриттів, а лише лямбда-функцію, це не допомагає пояснити, чому закриття було б корисним.)
Nathan Tuggy

Натане, цей приклад коду - це закриття у Swift. Ви можете запитати когось іншого, якщо ви сумніваєтесь у мені. Отже, якщо це буде закриття, ви б проголосували за це?
Бендрікс

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