После прочтения многих постов, объясняющих здесь замыкания, мне все еще не хватает ключевой концепции: зачем писать замыкания? Какую конкретную задачу будет выполнять программист, которая лучше всего подходит для закрытия?
Примерами замыканий в Swift являются обращения к NSUrl и использование обратного геокодера. Вот один из таких примеров. К сожалению, эти курсы просто представляют собой закрытие; они не объясняют, почему решение кода написано как замыкание.
Пример проблемы программирования в реальном мире, которая могла бы заставить мой мозг сказать «ага, я должен написать закрытие для этого», был бы более информативным, чем теоретическая дискуссия. На этом сайте нет недостатка в теоретических дискуссиях.
Ответы:
Прежде всего, нет ничего невозможного без использования замыканий. Вы всегда можете заменить замыкание объектом, реализующим определенный интерфейс. Это всего лишь вопрос краткости и уменьшения сцепления.
Во-вторых, имейте в виду, что замыкания часто используются ненадлежащим образом, когда простая ссылка на функцию или другая конструкция была бы более понятной. Вы не должны брать каждый пример, который вы считаете лучшим.
Когда замыкания действительно сияют над другими конструкциями, это когда используются функции более высокого порядка, когда вам действительно нужно сообщить состояние и вы можете сделать его однострочным, как в этом примере JavaScript со страницы википедии о замыканиях :
Здесь
threshold
очень кратко и естественно сообщается от того, где это определено, где это используется. Его область действия точно ограничена настолько малой, насколько это возможно.filter
не нужно писать, чтобы можно было передавать данные, определенные клиентом, как порог. Нам не нужно определять какие-либо промежуточные структуры с единственной целью сообщения порога в этой маленькой функции. Это полностью автономно.Вы можете написать это без замыкания, но это потребует намного больше кода, и будет сложнее следовать. Кроме того, JavaScript имеет довольно многословный лямбда-синтаксис. Например, в Scala все тело функции будет выглядеть так:
Однако если вы можете использовать ECMAScript 6 , благодаря функциям жирной стрелки даже код JavaScript становится намного проще и может быть фактически помещен в одну строку.
В своем собственном коде ищите места, где вы генерируете много шаблонов, чтобы просто передавать временные значения из одного места в другое. Это отличные возможности для замены на закрытие.
источник
bestSellingBooks
код, так и наfilter
код, например, на конкретный интерфейс или аргумент пользовательских данных, чтобы иметь возможность передаватьthreshold
данные. Это связывает две функции вместе гораздо менее многократно.В качестве объяснения я собираюсь позаимствовать некоторый код из этого отличного поста в блоге о замыканиях . Это JavaScript, но это язык, на котором большинство постов в блогах говорят о замыканиях, потому что замыкания так важны в JavaScript.
Допустим, вы хотите отобразить массив в виде таблицы HTML. Вы можете сделать это так:
Но вы во власти JavaScript, как каждый элемент в массиве будет отображаться. Если вы хотите контролировать рендеринг, вы можете сделать это:
И теперь вы можете просто передать функцию, которая возвращает желаемый рендеринг.
Что если вы хотите отобразить промежуточную сумму в каждой строке таблицы? Вам понадобится переменная для отслеживания этой суммы, не так ли? Замыкание позволяет вам написать функцию рендерера, которая закрывает переменную промежуточного итога, и позволяет вам написать рендерер, который может отслеживать итоговую сумму:
Волшебство, которое происходит здесь, заключается в том, что
renderInt
сохраняет доступ кtotal
переменной, даже еслиrenderInt
она неоднократно вызывается и завершается.В более традиционно объектно-ориентированном языке, чем JavaScript, вы могли бы написать класс, содержащий эту переменную total, и передать его вместо создания замыкания. Но закрытие - гораздо более мощный, чистый и элегантный способ сделать это.
Дальнейшее чтение
источник
Цель
closures
состоит в том, чтобы просто сохранить государство; отсюда и названиеclosure
- оно закрывается над государством. Для простоты дальнейшего объяснения я буду использовать Javascript.Обычно у вас есть функция
где область действия переменной (ей) связана с этой функцией. Таким образом, после выполнения переменная
txt
выходит из области видимости. Нет способа получить доступ или использовать его после завершения функции.Замыкания - это языковые конструкции, которые позволяют, как было сказано ранее, сохранять состояние переменных и таким образом продлевать область видимости.
Это может быть полезно в разных случаях. Одним из вариантов использования является построение функций более высокого порядка .
Простой, но, правда, не слишком полезный пример:
Вы определяете функцию
makedadder
, которая принимает один параметр в качестве входных данных и возвращает функцию . Существует внешняя функцияfunction(a){}
и внутренняя.function(b){}{}
Кроме того, вы определяете (неявно) другую функциюadd5
в результате вызова функции более высокого порядкаmakeadder
.makeadder(5)
возвращает анонимную ( внутреннюю ) функцию, которая, в свою очередь, принимает 1 параметр и возвращает сумму параметра внешней функции и параметра внутренней функции.Трик в том, что при возвращении на внутреннюю функцию, которая делает фактическое добавление, объем параметра внешней функции (
a
) сохраняются.add5
помнит , что параметрa
был5
.Или показать хотя бы один полезный пример:
Другим распространенным случаем использования является так называемое выражение вызываемой функции IIFE =. В javascript очень распространено подделывать закрытые переменные-члены. Это делается с помощью функции, которая создает частную область видимости =
closure
, потому что она сразу после вызова определения вызывается. Структура естьfunction(){}()
. Обратите внимание на скобки()
после определения. Это позволяет использовать его для создания объекта с раскрытием шаблона модуля . Хитрость заключается в создании области действия и возвращении объекта, который имеет доступ к этой области после выполнения IIFE.Пример Адди выглядит так:
Возвращаемый объект имеет ссылки на функции (например
publicSetName
), которые в свою очередь имеют доступ к «закрытым» переменнымprivateVar
.Но это более особые случаи использования Javascript.
Для этого есть несколько причин. Можно подумать, что это естественно для него, поскольку он следует функциональной парадигме . Или в Javascript: это просто необходимость полагаться на замыкания, чтобы обойти некоторые странности языка.
источник
Существует два основных варианта использования замыканий:
Асинхронность. Допустим, вы хотите выполнить задачу, которая займет некоторое время, а затем что-то сделать, когда это будет сделано. Вы можете либо заставить свой код ждать его выполнения, что блокирует дальнейшее выполнение и может заставить вашу программу перестать отвечать на запросы, либо вызвать вашу задачу асинхронно и сказать «начать эту длинную задачу в фоновом режиме, а когда она завершится, выполнить это закрытие», где замыкание содержит код, который нужно выполнить после завершения.
Callbacks. Они также известны как «делегаты» или «обработчики событий» в зависимости от языка и платформы. Идея состоит в том, что у вас есть настраиваемый объект, который в определенных четко определенных точках будет выполнять событие , которое запускает замыкание, передаваемое кодом, который его устанавливает. Например, в пользовательском интерфейсе вашей программы у вас может быть кнопка, и вы даете ей закрытие, содержащее код, который будет выполнен, когда пользователь нажимает на кнопку.
Есть несколько других применений для замыканий, но это два основных.
источник
Пара других примеров:
Сортировка
Большинство функций сортировки работают путем сравнения пар объектов. Необходима некоторая техника сравнения. Ограничение сравнения конкретным оператором означает довольно негибкий вид. Гораздо лучший подход - получить функцию сравнения в качестве аргумента функции сортировки. Иногда функция сравнения без сохранения состояния работает нормально (например, сортировка списка чисел или имен), но что, если для сравнения требуется состояние?
Например, рассмотрите возможность сортировки списка городов по расстоянию до определенного места. Уродливое решение - сохранить координаты этого местоположения в глобальной переменной. Это делает саму функцию сравнения без сохранения состояния, но за счет глобальной переменной.
Этот подход исключает одновременную сортировку нескольких потоков по одному и тому же списку городов по их расстоянию до двух разных мест. Закрытие, которое охватывает местоположение, решает эту проблему, и это избавляет от необходимости глобальной переменной.
Случайные числа
В оригинале
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), среднее значение и стандартное отклонение в качестве состояния. Это, по крайней мере, концептуально, замыкание, которое принимает замыкание, которое принимает замыкание в качестве аргумента.
источник
Замыкания эквивалентны объектам, реализующим метод run (), и, наоборот, объекты можно эмулировать с замыканиями.
Преимущество замыканий в том, что их можно легко использовать везде, где вы ожидаете функцию: функции высшего порядка, простые обратные вызовы (или шаблон стратегии). Вам не нужно определять интерфейс / класс для создания специальных замыканий.
Преимущество объектов заключается в возможности более сложных взаимодействий: нескольких методов и / или различных интерфейсов.
Таким образом, использование замыкания или объектов - это в основном вопрос стиля. Вот пример вещей, которые замыкания делают легкими, но неудобными для реализации с объектами:
По сути, вы инкапсулируете скрытое состояние, доступ к которому осуществляется только через глобальные замыкания: вам не нужно ссылаться на какой-либо объект, используйте только протокол, определенный тремя функциями.
Я доверяю первому замечанию supercat о том, что в некоторых языках можно точно контролировать время жизни объектов, тогда как для замыканий это не так. Однако в случае языков с сборкой мусора время жизни объектов, как правило, не ограничено, и, таким образом, можно создать замыкание, которое можно вызывать в динамическом контексте, где его не следует вызывать (чтение из замыкания после потока. закрыто, например).
Тем не менее, довольно просто предотвратить такое неправильное использование путем захвата управляющей переменной, которая будет защищать выполнение замыкания. Точнее, вот что я имею в виду (в Common Lisp):
Здесь мы берем обозначение функции
function
и возвращаем два замыкания, каждое из которых захватывает локальную переменную с именемactive
:function
, только когдаactive
это правдаaction
кnil
, акаfalse
.Вместо этого
(when active ...)
, конечно, возможно иметь(assert active)
выражение, которое может генерировать исключение в случае, если замыкание вызывается, когда оно не должно быть. Кроме того, имейте в виду, что небезопасный код уже может выдать исключение сам по себе при плохом использовании, поэтому вам редко нужна такая оболочка.Вот как вы бы это использовали:
Обратите внимание, что дезактивирующие замыкания могут также передаваться и другим функциям; здесь локальные
active
переменные не разделяются междуf
иg
; Кроме того , в дополнение кactive
,f
относится только кobj1
иg
относится только кobj2
.Другой момент, упомянутый суперкатом, заключается в том, что замыкания могут привести к утечкам памяти, но, к сожалению, это касается практически всего в средах с сборкой мусора. Если они доступны, это можно решить с помощью слабых указателей (само замыкание может храниться в памяти, но не предотвращает сбор мусора других ресурсов).
источник
List<T>
в (гипотетическом классе)TemporaryMutableListWrapper<T>
и предоставляет его внешнему коду, можно гарантировать, что, если он аннулирует оболочку, внешний код больше не будет иметь никакого способа манипулироватьList<T>
. Можно спроектировать замыкания так, чтобы они стали недействительными, когда они достигли ожидаемой цели, но это вряд ли удобно. Закрытия существуют для того, чтобы сделать определенные шаблоны удобными, и усилия, необходимые для их защиты, сведут на нет это.Ничего, что еще не было сказано, но, возможно, более простой пример.
Вот пример JavaScript с использованием тайм-аутов:
Что происходит здесь, так это то, что при
delayedLog()
вызове он возвращается сразу после установки времени ожидания, и время ожидания продолжает снижаться в фоновом режиме.Но когда тайм-аут истекает и вызывает
fire()
функцию, консоль отобразит тот,message
который был первоначально переданdelayedLog()
, потому что он все еще доступенfire()
через замыкание. Вы можете звонитьdelayedLog()
сколько угодно, с разными сообщениями и задержками каждый раз, и это будет правильно.Но давайте представим, что в JavaScript нет замыканий.
Одним из способов было бы сделать
setTimeout()
блокировку - больше похожую на «спящую» функцию - такdelayedLog()
что область действия не исчезнет, пока не истечет время ожидания. Но блокировать все не очень приятно.Другим способом было бы поместить
message
переменную в другую область, которая будет доступна после того,delayedLog()
как область исчезнет.Вы можете использовать глобальные (или, по крайней мере, «более широкие») переменные, но вам придется выяснить, как отслеживать, какое сообщение идет с каким тайм-аутом. Но это не может быть просто последовательная очередь FIFO, потому что вы можете установить любую задержку, какую захотите. Так что это может быть «первый вошел, третий вышел» или что-то в этом роде. Поэтому вам понадобятся другие средства для привязки временной функции к нужным переменным.
Вы можете создать экземпляр объекта тайм-аута, который «группирует» таймер с сообщением. Контекст объекта - это более или менее область видимости. Тогда вы бы запустили таймер в контексте объекта, чтобы у него был доступ к нужному сообщению. Но вам придется хранить этот объект, потому что без каких-либо ссылок он будет собирать мусор (без замыканий также не будет никаких неявных ссылок на него). И вам придется удалить объект, как только у него истечет время ожидания, в противном случае он просто останется. Таким образом, вам понадобится какой-то список объектов тайм-аута и периодически проверяйте его на предмет «потраченных» объектов для удаления - или объекты будут добавлять и удалять себя из списка, и
Так что ... да, это становится скучно.
К счастью, вам не нужно использовать более широкую область видимости или оборачивать объекты только для того, чтобы сохранить определенные переменные. Поскольку JavaScript имеет замыкания, у вас уже есть именно тот объем, который вам нужен. Область, которая дает вам доступ к
message
переменной, когда вам это нужно. И из-за этого вы можете сойти с рук,delayedLog()
как написано выше.источник
message
заключен в область действия функцииfire
и поэтому упоминается в дальнейших вызовах; но это делает это случайно, так сказать. Технически это закрытие. +1 в любом случае;)makeadder
пример выше, который, на мой взгляд, выглядит примерно так же. Вы возвращаете «карри» функцию, которая принимает один аргумент вместо двух; используя те же средства, я создаю функцию, которая принимает нулевые аргументы. Я просто не возвращаю его, а передаюsetTimeout
.message
вfire
генерирует замыкание. И когда он вызывается,setTimeout
он использует сохраненное состояние.PHP может быть использован, чтобы помочь показать реальный пример на другом языке.
В общем, я регистрирую функцию, которая будет выполняться для URI / api / users . Это на самом деле функция промежуточного программного обеспечения, которая в конечном итоге хранится в стеке. Другие функции будут обернуты вокруг него. Совсем как Node.js / Express.js .
Инъекции зависимостей контейнера можно ( с помощью пункта использования) внутри функции , когда она вызывается. Можно создать какой-то класс действий маршрута, но этот код оказывается проще, быстрее и проще в обслуживании.
источник
Замыкание является частью произвольного кода, включая переменные, которые могут быть обработаны как данные первым классом.
Тривиальный пример - старый добрый qsort: это функция для сортировки данных. Вы должны дать ему указатель на функцию, которая сравнивает два объекта. Таким образом, вы должны написать функцию. Эта функция может нуждаться в параметризации, что означает, что вы задаете ей статические переменные. Что означает, что это не безопасно для потоков. Вы в DS. Таким образом, вы пишете альтернативу, которая принимает замыкание вместо указателя на функцию. Вы мгновенно решаете проблему параметризации, потому что параметры становятся частью замыкания. Вы делаете свой код более читабельным, потому что пишете, как объекты сравниваются непосредственно с кодом, который вызывает функцию сортировки.
Существует множество ситуаций, когда вы хотите выполнить какое-либо действие, требующее большого количества кода, а также один крошечный, но важный фрагмент кода, который необходимо адаптировать. Вы избегаете стандартного кода, написав функцию один раз, которая принимает параметр замыкания и выполняет весь шаблонный код вокруг него, а затем вы можете вызвать эту функцию и передать код, который будет адаптирован как замыкание. Очень компактный и читаемый способ написания кода.
У вас есть функция, в которой необходимо выполнять некоторый нетривиальный код в разных ситуациях. Используется для создания дублирования кода или искаженного кода, так что нетривиальный код будет присутствовать только один раз. Trivial: Вы назначаете замыкание переменной и вызываете его наиболее очевидным образом, где бы оно ни было необходимо.
Многопоточность: iOS / MacOS X имеет такие функции, как «выполнить это замыкание в фоновом потоке», «... в основном потоке», «... в основном потоке, через 10 секунд». Это делает многопоточность тривиальной .
Асинхронные вызовы: это то, что видел ОП. Любой вызов, который получает доступ к Интернету, или что-либо еще, что может занять время (например, чтение координат GPS), - это то, что вы не можете дождаться результата. Итак, у вас есть функции, которые делают вещи в фоновом режиме, а затем вы передаете закрытие, чтобы сказать им, что делать, когда они закончат.
Это небольшое начало. Пять ситуаций, когда замыкания являются революционными с точки зрения создания компактного, удобочитаемого, надежного и эффективного кода.
источник
Закрытие - это сокращенный способ написания метода, в котором он будет использоваться. Это избавляет вас от необходимости декларировать и писать отдельный метод. Это полезно, когда метод будет использоваться только один раз, а определение метода короткое. Преимущества уменьшаются при вводе текста, поскольку нет необходимости указывать имя функции, тип возвращаемого значения или модификатор доступа. Кроме того, при чтении кода вам не нужно искать определение метода в другом месте.
Выше приведено краткое изложение «Понимание лямбда-выражений» Дэна Авидара.
Это прояснило использование замыканий для меня, потому что оно проясняет альтернативы (замыкание и метод) и преимущества каждого.
Следующий код используется один раз и только один раз во время настройки. Запись его на место под viewDidLoad избавляет от необходимости искать его в другом месте и сокращает размер кода.
Кроме того, он обеспечивает выполнение асинхронного процесса без блокировки других частей программы, а закрытие сохранит значение для повторного использования в последующих вызовах функций.
Еще одно закрытие; этот захватывает значение ...
источник