Закрытия: почему они так полезны?

Мои 2 pennies:-

 collection.Count(v => (v.PropertyToUpdate = newValue) == null);
55
задан Peter Mortensen 9 October 2009 в 22:49
поделиться

8 ответов

Замыкания не дают вам никаких дополнительных возможностей.

Все, что вы можете достичь с их помощью, вы можете достичь и без них.

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

Позвольте мне привести краткий пример возможного использования Java:

    button.addActionListener(new ActionListener() {
        @Override public void actionPerformed(ActionEvent e) {
            System.out.println("Pressed");
        }
    });

Будет заменен (если бы Java имел закрытие) на:

button.addActionListener( { System.out.println("Pressed"); } );
28
ответ дан 26 November 2019 в 17:46
поделиться

Вы можете рассматривать это как обобщение класса.

Ваш класс хранит некоторое состояние. У него есть некоторые переменные-члены, которые могут использовать его методы.

Замыкание - это просто более удобный способ предоставить функции доступ к локальному состоянию.

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

Когда вы определяете метод члена на традиционном языке ООП, его закрытие "все члены, видимые в этот класс ».

Языки с« правильной »поддержкой замыкания просто обобщают это, поэтому закрытие функции - это« все переменные, видимые здесь ». Если « здесь » - это класс, то у вас есть традиционный метод класса.

Если « здесь » находится внутри другой функции, то у вас есть то, что функциональные программисты считают закрытием. Теперь ваша функция может получить доступ ко всему, что было видно в родительской функции.

Так что это просто обобщение, снимающее глупое ограничение, что «функции могут быть определены только внутри классов», но сохраняя идею, что «функции могут видеть любые переменные. видны в том месте, где они объявлены ».

67
ответ дан 26 November 2019 в 17:46
поделиться

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

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

[edit - пример кода на основе комментария выше]

Java:

List<Integer> numbers = ...;
List<Integer> positives = new LinkedList<Integer>();
for (Integer number : integers) {
    if (number >= 0) {
        positives.add(number);
    }
}

Scala (пропущены некоторые другие тонкости, такие как вывод типов и подстановочные знаки, поэтому мы сравниваем только эффект закрытия):

val numbers:List[Int] = ...
val positives:List[Int] = numbers.filter(i:Int => i >= 0)
5
ответ дан 26 November 2019 в 17:46
поделиться

Вот несколько интересных статей:

3
ответ дан 26 November 2019 в 17:46
поделиться

Замыкания очень хорошо вписываются в объектно-ориентированный мир.

В качестве примера рассмотрим C # 3.0: он имеет замыкания и многие другие функциональные аспекты, но по-прежнему является очень объектно-ориентированным языком.

1266] По моему опыту, функциональные аспекты C # имеют тенденцию оставаться в рамках реализации членов класса, а не столько как часть общедоступного API, который в конечном итоге раскрывают мои объекты.

Таким образом, использование замыканий, как правило, является деталями реализации в объектно-ориентированном коде.

Я использую их постоянно, как показывает этот фрагмент кода из одного из наших модульных тестов (против Moq ):

var typeName = configuration.GetType().AssemblyQualifiedName;

var activationServiceMock = new Mock<ActivationService>();
activationServiceMock.Setup(s => s.CreateInstance<SerializableConfigurationSection>(typeName)).Returns(configuration).Verifiable();

Было бы довольно сложно указать входное значение (typeName) как часть ожидания Mock, если бы не функция закрытия C #.

3
ответ дан 26 November 2019 в 17:46
поделиться

Возможно, в мире компилируемого программирования преимущества замыканий менее заметны. В JavaScript закрытие невероятно мощно. Это происходит по двум причинам:

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

2) JavaScript это лямбда-язык. Это означает, что в функциях JavaScript являются объектами первого класса, которые определяют область действия, а область видимости родительской функции доступна для дочерних объектов. Это важно, поскольку переменная может быть объявлена ​​в родительской функции, использоваться в дочерней функции и сохранять значение даже после возврата из дочерней функции.

-2
ответ дан 26 November 2019 в 17:46
поделиться

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

Например, обычное использование в JavaScript - запуск HTTP-запроса. Тот, кто его запускает, вероятно, хочет контролировать, что происходит, когда приходит ответ. Итак, вы сделаете что-то вроде этого:

function sendRequest() {
  var requestID = "123";
  Ext.Ajax.request({
    url:'/myUrl'
    success: function(response) {
      alert("Request " + requestID + " returned");
    }
  });
}

Из-за закрытий JavaScript переменная "requestID" захватывается внутри функции успеха. Это показывает, как вы можете написать функции запроса и ответа в одном месте и совместно использовать переменные между ними. Без закрытий вы

28
ответ дан 26 November 2019 в 17:46
поделиться

Жалко, люди больше не изучают Smalltalk в edu; там замыкания используются для структур управления, обратных вызовов, перечисления коллекций, обработки исключений и многого другого. В качестве небольшого приятного примера, вот поток обработчика действий рабочей очереди (в Smalltalk):

|actionQueue|

actionQueue := SharedQueue new.
[
    [
        |a|

        a := actionQueue next.
        a value.
    ] loop
] fork.

actionQueue add: [ Stdout show: 1000 factorial ].

и, для тех, кто не умеет читать Smalltalk, то же самое в синтаксисе JavaScript:

var actionQueue;

actionQueue = new SharedQueue;
function () {
    for (;;) {
        var a;

        a = actionQueue.next();
        a();
    };
}.fork();

actionQueue.add( function () { Stdout.show( 1000.factorial()); });

(ну, как вы видите: синтаксис помогает в чтение кода)

Изменить: обратите внимание, как на actionQueue ссылаются изнутри блоков, что работает даже для разветвленного блока потока. Вот почему укупорочные средства так просты в использовании.

4
ответ дан 26 November 2019 в 17:46
поделиться