Когда использовать закрытие?
Я видел образцы закрытия от Что такое "Закрытие" ?
Может ли кто-нибудь предоставить простой пример использования закрытия?
В частности, сценарии, в которых замыкание имеет смысл?
Предположим, что язык не имеет поддержки закрытия, как бы все еще удалось достичь подобной вещи?
Чтобы никого не обидеть, отправьте образцы кода на языке, таком как С#, python, javascript, ruby и т.д.
Извините, я пока не понимаю функциональные языки.
Ответы
Ответ 1
Закрытие - просто отличные инструменты. Когда их использовать? Каждый раз, когда вам нравится... Как уже было сказано, альтернативой является создание класса; например, pre С# 2.0, создание параметризованной нити было реальной борьбой. С С# 2.0 вам даже не нужен "ParameterizedThreadStart", который вы только что делаете:
string name = // blah
int value = // blah
new Thread((ThreadStart)delegate { DoWork(name, value);}); // or inline if short
Сравните это с созданием класса с именем и значением
Или аналогично с поиском списка (используя лямбда в этот раз):
Person person = list.Find(x=>x.Age > minAge && x.Region == region);
Опять же - альтернативой было бы написать класс с двумя свойствами и методом:
internal sealed class PersonFinder
{
public PersonFinder(int minAge, string region)
{
this.minAge = minAge;
this.region = region;
}
private readonly int minAge;
private readonly string region;
public bool IsMatch(Person person)
{
return person.Age > minAge && person.Region == region;
}
}
...
Person person = list.Find(new PersonFinder(minAge,region).IsMatch);
Это довольно сопоставимо с тем, как компилятор делает это под капотом (на самом деле он использует общедоступные поля для чтения/записи, а не личные readonly).
Самое большое предостережение с захватами С# - это просмотр области; например:
for(int i = 0 ; i < 10 ; i++) {
ThreadPool.QueueUserWorkItem(delegate
{
Console.WriteLine(i);
});
}
Это может не печатать то, что вы ожидаете, поскольку переменная я используется для каждого. Вы могли видеть любую комбинацию повторов - даже 10 10. Вам нужно тщательно просмотреть захваченные переменные в С#:
for(int i = 0 ; i < 10 ; i++) {
int j = i;
ThreadPool.QueueUserWorkItem(delegate
{
Console.WriteLine(j);
});
}
Здесь каждый j захватывается отдельно (т.е. другой экземпляр класса, сгенерированный компилятором).
У Jon Skeet есть хорошая запись в блоге, охватывающая С# и закрытие java здесь; или более подробно, см. его книгу С# в глубине, которая содержит целую главу.
Ответ 2
Я согласен с предыдущим ответом "все время". Когда вы программируете на функциональном языке или на любом языке, где яблоки и закрытие являются общими, вы используете их, даже не замечая. Это похоже на вопрос: "Каков сценарий для функции?" или "что такое сценарий для цикла?" Это не значит, что исходный вопрос звучит глупо, а указывать на то, что существуют конструкции на языках, которые вы не определяете с точки зрения конкретных сценариев. Вы просто используете их все время, для всего, это вторая природа.
Это как-то напоминает:
Почитаемый мастер Qc Na шел со своим учеником Антоном. Надеюсь на побудить мастера к обсуждению, Антон сказал: "Учитель, я слышал, что объекты - очень хорошая вещь - это это правда?" Qc Na смотрел с жалостью на его ученик и ответил: "Глупый ученики - объекты просто бедны закрытие человека".
Обеспокоенный, Антон ушел от его хозяин и вернулся в свою камеру, намерение изучить закрытие. Он внимательно прочитайте всю "Лямбду: Ultimate..." и ее двоюродных братьев, и Преобразователь схемы с закрытая система объектов. Он многому научились и с нетерпением ждали информируя своего хозяина о его прогрессе.
На следующей прогулке с Qc Na, Антон попытался произвести впечатление на своего хозяина говоря: "Учитель, я усердно изучили этот вопрос, и теперь понимаем что объекты действительно являются бедными людьми закрытия". Qc Na ответил нажатием Антон с палкой, говоря: "Когда вы узнаете? Закрытие является плохим человека". В этот момент Антон стал просвещенным.
(http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html)
Ответ 3
Самый простой пример использования закрытий - это нечто вроде currying. В принципе, предположим, что мы имеем функцию f()
, которая при вызове с двумя аргументами a
и b
добавляет их вместе. Итак, в Python мы имеем:
def f(a, b):
return a + b
Но позвольте сказать, ради аргумента, что мы хотим только называть f()
одним аргументом за раз. Итак, вместо f(2, 3)
мы хотим f(2)(3)
. Это можно сделать так:
def f(a):
def g(b): # Function-within-a-function
return a + b # The value of a is present in the scope of g()
return g # f() returns a one-argument function g()
Теперь, когда мы вызываем f(2)
, мы получаем новую функцию g()
; эта новая функция переносит с ней переменные из области f()
, и, как говорят, она закрывается над этими переменными, следовательно, термин "замыкание". Когда мы вызываем g(3)
, переменную a
(которая связана определением f
) обращается к g()
, возвращая 2 + 3 => 5
Это полезно в нескольких сценариях. Например, если бы у меня была функция, которая принимала большое количество аргументов, но только некоторые из них были полезны мне, я мог бы написать такую общую функцию:
def many_arguments(a, b, c, d, e, f, g, h, i):
return # SOMETHING
def curry(function, **curry_args):
# call is a closure which closes over the environment of curry.
def call(*call_args):
# Call the function with both the curry args and the call args, returning
# the result.
return function(*call_args, **curry_args)
# Return the closure.
return call
useful_function = curry(many_arguments, a=1, b=2, c=3, d=4, e=5, f=6)
useful_function
теперь является функцией, которая требует только 3 аргумента, а не 9. Я не хочу повторять себя, а также создал общее решение; если я напишу еще одну функцию с несколькими аргументами, я снова смогу использовать инструмент curry
.
Ответ 4
Как правило, если у вас нет закрытий, нужно определить класс для переноса с ним эквивалента среды замыкания и передать его.
Например, на языке, подобном Lisp, можно определить функцию, которая возвращает функцию (с закрытой средой), чтобы добавить к ней заранее предопределенное количество:
(defun make-adder (how-much)
(lambda (x)
(+ x how-much)))
и используйте его следующим образом:
cl-user(2): (make-adder 5)
#<Interpreted Closure (:internal make-adder) @ #x10009ef272>
cl-user(3): (funcall * 3) ; calls the function you just made with the argument '3'.
8
На языке без закрытий вы бы сделали что-то вроде этого:
public class Adder {
private int howMuch;
public Adder(int h) {
howMuch = h;
}
public int doAdd(int x) {
return x + howMuch;
}
}
а затем используйте его следующим образом:
Adder addFive = new Adder(5);
int addedFive = addFive.doAdd(3);
// addedFive is now 8.
Закрытие неявно несет в себе свою среду; вы легко обращаетесь к этой среде изнутри исполняющей части (лямбда). Без закрытия вы должны сделать эту среду явной.
Это должно объяснить вам, когда вы будете использовать закрытие: все время. Большинство экземпляров, в которых экземпляр класса создается для переноса с ним некоторого состояния из другой части вычисления и применяет его в другом месте, элегантно заменяется закрытием на языках, которые их поддерживают.
Можно реализовать объектную систему с закрытием.
Ответ 5
Вот пример из стандартной библиотеки Python, inspect.py. В настоящее время он читает
def strseq(object, convert, join=joinseq):
"""Recursively walk a sequence, stringifying each element."""
if type(object) in (list, tuple):
return join(map(lambda o, c=convert, j=join: strseq(o, c, j), object))
else:
return convert(object)
Это, как параметры, функция преобразования и функция соединения, и рекурсивно просматривает списки и кортежи. Рекурсия реализуется с использованием map(), где первым параметром является функция. Код предшествует поддержке закрытий в Python, поэтому ему нужны два дополнительных аргумента по умолчанию, чтобы передать конверсию и присоединиться к рекурсивному вызову. С закрытием это читает
def strseq(object, convert, join=joinseq):
"""Recursively walk a sequence, stringifying each element."""
if type(object) in (list, tuple):
return join(map(lambda o: strseq(o, convert, join), object))
else:
return convert(object)
В языках OO вы обычно слишком часто не используете блокировки, так как вы можете использовать объекты для передачи методов состояния и привязки, когда ваш язык имеет их. Когда у Python не было замыканий, люди говорили, что Python эмулирует замыкания с объектами, тогда как Lisp эмулирует объекты с закрытием. В качестве примера из IDLE (ClassBrowser.py):
class ClassBrowser: # shortened
def close(self, event=None):
self.top.destroy()
self.node.destroy()
def init(self, flist):
top.bind("<Escape>", self.close)
Здесь self.close - это обратный вызов без параметра, вызываемый при нажатии Escape. Однако для близкой реализации нужны параметры, а именно self, а затем self.top, self.node. Если у Python не были связанные методы, вы могли бы написать
class ClassBrowser:
def close(self, event=None):
self.top.destroy()
self.node.destroy()
def init(self, flist):
top.bind("<Escape>", lambda:self.close())
Здесь лямбда получит "я" не от параметра, а из контекста.
Ответ 6
В Lua и Python это очень естественно делать, когда "просто кодирование", потому что в тот момент, когда вы ссылаетесь на то, что не является параметром, вы делаете закрытие. (поэтому большинство из них будут довольно скучными, как примеры.)
Что касается конкретного случая, представьте систему отмены/повтора, где этапы представляют собой пары (undo(), redo()) замыканий. Более громоздкие способы сделать это могут быть следующими: (a) Сделать неуязвимые классы специальным методом с универсальными доркими аргументами или (б) подкласса UnReDoOperation разным раза.
Другим конкретным примером являются бесконечные списки: вместо работы с обобщенными контейнерами вы запускаете функцию, которая извлекает следующий элемент. (это часть мощности итераторов). В этом случае вы можете либо сохранить немного немного состояния (следующее целое число, для целых чисел, не являющихся неотрицательными целыми числами, либо как аналогичное) или ссылку на позицию в фактический контейнер. В любом случае, это функция, которая ссылается на то, что находится вне себя. (в случае с бесконечным списком переменные состояния должны быть замыкающими переменными, поскольку в противном случае они были бы чистыми для каждого вызова)
Ответ 7
Мне говорят, что в haskell больше применений, но я только имел удовольствие использовать закрытие в javascript, а в javascript я не очень понимаю. Мой первый инстинкт состоял в том, чтобы кричать "о, нет, а не снова", в каком беспорядке должно быть выполнение, чтобы сделать работу закрытия.
После того, как я прочитал о том, как были реализованы блокировки (в любом случае, в javascript), мне это покажется не таким уж плохим, и реализация кажется мне несколько элегантной, по крайней мере для меня.
Но из этого я понял, что "закрытие" на самом деле не лучшее слово для описания концепции. Я думаю, что его лучше назвать "областью полета".
Ответ 8
Как один из предыдущих ответов, вы часто обнаруживаете, что используете их, не заметив, что вы есть.
Дело в том, что они очень часто используются при настройке обработки событий пользовательского интерфейса для повторного использования кода, сохраняя при этом доступ к контексту пользовательского интерфейса. Здесь пример того, как определение функции анонимного обработчика для события click создает закрытие, которое включает параметры button
и color
функции setColor()
:
function setColor(button, color) {
button.addEventListener("click", function()
{
button.style.backgroundColor = color;
}, false);
}
window.onload = function() {
setColor(document.getElementById("StartButton"), "green");
setColor(document.getElementById("StopButton"), "red");
}
Примечание: для точности стоит отметить, что закрытие фактически не создается до тех пор, пока функция setColor()
не выйдет.
Ответ 9
В этой статье представлены два примера того, где закрытие действительно полезно:
Closure