Подтвердить что ты не робот

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

Я видел образцы закрытия от Что такое "Закрытие" ?

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

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

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

Чтобы никого не обидеть, отправьте образцы кода на языке, таком как С#, python, javascript, ruby ​​и т.д.
Извините, я пока не понимаю функциональные языки.

4b9b3361

Ответ 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