Когда использовать закрытие?

Я видел образцы закрытия из - Что такое «закрытие»?

Может ли кто-нибудь привести простой пример того, когда использовать закрытие?

В частности, сценарии, при которых закрытие имеет смысл?

Предположим, что в языке нет поддержки замыкания, как можно добиться подобного?

Чтобы никого не обидеть, разместите образцы кода на таких языках, как c #, python, javascript, ruby ​​и т. Д.
Прошу прощения, я пока не понимаю функциональные языки.


person shahkalpeshp    schedule 02.11.2008    source источник
comment
Я думаю, вы можете взглянуть на приведенную ниже ссылку на шаблоны проектирования, которые позволяют нам кратко реализовать замыкания сообщение о шаблонах проектирования закрытия   -  person Sudarshan    schedule 13.10.2012
comment
Прискорбно для сообщества, когда подобные вопросы закрываются. Это вполне разумный вопрос, который возникнет во многих закрывающих занятиях - трудная тема. Понимание реальных случаев использования - отличный способ ускорить процесс изучения сложной темы. К счастью, до его закрытия было много отличных ответов.   -  person Matthew    schedule 26.05.2017
comment
этот вопрос не должен был закрываться, я иногда ТАК не получаю :(   -  person Vivek Shukla    schedule 24.06.2021


Ответы (8)


Замыкания - просто отличные инструменты. Когда их использовать? В любое время, когда захотите ... Как уже было сказано, альтернатива - написать класс; например, до C # 2.0 создание параметризованного потока было настоящей проблемой. С C # 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);

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

Самая большая оговорка, связанная с захватами C #, - это следить за областью видимости; Например:

        for(int i = 0 ; i < 10 ; i++) {
            ThreadPool.QueueUserWorkItem(delegate
            {
                Console.WriteLine(i);
            });
        }

Это может не напечатать то, что вы ожидаете, поскольку для каждого используется переменная i. Вы могли увидеть любую комбинацию повторов - даже 10 десятков. Вам необходимо тщательно определить область захваченных переменных в C #:

        for(int i = 0 ; i < 10 ; i++) {
            int j = i;
            ThreadPool.QueueUserWorkItem(delegate
            {
                Console.WriteLine(j);
            });
        }

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

У Джона Скита есть хорошая запись в блоге, посвященная закрытию C # и java здесь; или для получения более подробной информации см. его книгу C # in Depth, в которой есть целая глава о них.

person Marc Gravell    schedule 02.11.2008
comment
Слегка не по теме, но не могли бы вы подсказать, как произносится Person person = list.Find (x = ›x.Age› minAge && x.Region == region); если читать вслух? - person Dour High Arch; 02.11.2008
comment
назначить переменную person (типа Person) с помощью list.Find: x переходит в x.Age больше minAge и x.Region равно region. Хотя я, вероятно, сократил бы до: ... со списком. Найдите, где Возраст больше minAge и Регион равен региону .. Но идет к - это обычная интерпретация = ›. - person Marc Gravell; 03.11.2008
comment
В связи с этим вам может быть интересно ответить на stackoverflow.com/questions/1962539/ :) - person RCIX; 26.12.2009
comment
Думаю, дано общепринятое произношение оператора '= ›'. Например, «p =› p.name »будет читаться с p, p-dot-name. - person EightyOne Unite; 21.07.2010
comment
@ Stimul8d - Я никогда не слышал, чтобы это называлось так, но каждому свое. - person Marc Gravell; 21.07.2010

Я согласен с предыдущим ответом «все время». Когда вы программируете на функциональном языке или на любом языке, где лямбды и замыкания являются общими, вы используете их, даже не замечая. Это все равно что спросить: «Каков сценарий функции?» или "каков сценарий петли?" Это не для того, чтобы исходный вопрос казался глупым, а для того, чтобы указать на то, что в языках есть конструкции, которые вы не определяете в терминах конкретных сценариев. Вы просто используете их постоянно, для всего, это вторая натура.

Это чем-то напоминает:

Почтенный мастер Qc Na гулял со своим учеником Антоном. Надеясь побудить мастера к обсуждению, Антон сказал: «Мастер, я слышал, что предметы - это очень хорошая вещь - это правда?» Qc Na с жалостью посмотрел на своего ученика и ответил: «Глупый ученик, объекты - всего лишь закрытие для бедняков».

Наказанный, Антон попрощался с хозяином и вернулся в камеру, чтобы изучить закрытие. Он внимательно прочитал всю серию статей «Lambda: The Ultimate ...» и ее собратьев и реализовал небольшой интерпретатор Scheme с объектной системой на основе замыканий. Он многому научился и с нетерпением ждал возможности сообщить своему хозяину о своих успехах.

Во время следующей прогулки с Qc Na Антон попытался произвести впечатление на своего учителя, сказав: «Учитель, я усердно изучил этот вопрос и теперь понимаю, что объекты действительно являются закрытием для бедняков». Qc Na в ответ ударил Антона своей палкой и сказал: «Когда ты научишься? Замыкания - цель бедняков». В этот момент Антон просветился.

(http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html)

person Brian    schedule 02.11.2008
comment
Хороший. Поскольку функциональное программирование отличается (в смысле управления состоянием), естественно использовать замыкания. У меня не очень большой опыт в области функционального программирования. Я использую C #, он знакомит с этой концепцией. Пример все равно будет оценен. - person shahkalpeshp; 02.11.2008
comment
Глядя на приведенную вами аналогию (поскольку FP не имеет понятия объекта), состояние передается от функции к другой функции, и, таким образом, осуществляется управление состоянием всего приложения. правильно? - person shahkalpeshp; 02.11.2008
comment
В чистом FP ​​вы обычно вообще не заботитесь о состоянии. Когда вам действительно нужно состояние, вы делаете его явным и передаете. - person Matthias Benkard; 02.11.2008
comment
… И Common Lisp - это не чистый FP: он имеет изменяемое состояние. Просто у вас огромный выбор способов работы с состоянием :) - person Rich; 02.11.2008
comment
В Common Lisp есть CLOS (Common Lisp Object System), наиболее продуманная система параметров функции и возврата, которую я знаю, оптимизация хвостового вызова, расширенный LOOP ... Он гораздо более универсален, чем все эти чистые (узколобые?) Языки, и он по-прежнему имеет четко определенный и последовательный синтаксис. - person Svante; 19.11.2008
comment
Я не понимаю историю дзен? Как это они делают комплименты беднякам друг друга? - person zvolkov; 20.08.2009
comment
Есть пословица Го: возьми все четыре угла, и ты проиграешь. Пусть ваш противник захватит все четыре угла, и вы проиграете. Как будто. В этом примере говорится, что вы не должны полностью концентрироваться на углах, но вы также не можете игнорировать их. - person Almo; 28.06.2012

Самый простой пример использования замыканий - это каррирование. По сути, предположим, что у нас есть функция 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.

person zvoase    schedule 02.11.2008

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

Например, в таком языке, как 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.

Замыкание неявно несет с собой окружающую среду; вы легко обращаетесь к этой среде изнутри исполняющей части (лямбда). Без замыканий вы должны сделать эту среду явной.

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

Можно реализовать объектную систему с замыканиями.

person Rich    schedule 02.11.2008
comment
Спасибо за Ваш ответ. Не могли бы вы описать пример кода, в котором эффективно использовать замыкание? - person shahkalpeshp; 02.11.2008

Вот пример из стандартной библиотеки 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)

В объектно-ориентированных языках вы обычно не слишком часто используете замыкания, так как вы можете использовать объекты для передачи состояний и связанных методов, если они есть в вашем языке. Когда в 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())

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

person Martin v. Löwis    schedule 02.11.2008

В Lua и Python это очень естественно, когда вы «просто кодируете», потому что в тот момент, когда вы ссылаетесь на что-то, что не является параметром, вы закрываете его. (так что большинство из них будут довольно скучными в качестве примеров.)

Что касается конкретного случая, представьте себе систему отмены / возврата, в которой шаги представляют собой пары закрытий (undo (), redo ()). Более громоздкими способами сделать это могут быть: (а) сделать так, чтобы классы, не подлежащие изменению, имели специальный метод с универсально глупыми аргументами, или (б) создать подкласс UnReDoOperation бесчисленное количество раз.

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

person Anders Eurenius    schedule 02.11.2008
comment
Спасибо за Ваш ответ. Не могли бы вы описать пример кода, в котором эффективно использовать замыкание? - person shahkalpeshp; 02.11.2008

Мне сказали, что в haskell есть больше применений, но я имел удовольствие использовать замыкания только в javascript, а в javascript я не очень вижу в этом смысла. Моим первым побуждением было крикнуть: «О нет, только не снова!» Из-за того, какой беспорядок должна быть реализация, чтобы замыкания работали. После того, как я прочитал о том, как были реализованы замыкания (во всяком случае, в javascript), мне теперь это не кажется таким уж плохим, и реализация кажется несколько элегантной, по крайней мере, мне.

Но из этого я понял, что «закрытие» - не лучшее слово для описания концепции. Думаю, его лучше назвать «летающий прицел».

person stu    schedule 24.03.2011

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

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

person BitMask777    schedule 23.10.2013