Естественные проблемы для решения с помощью замыканий - PullRequest
2 голосов
/ 19 мая 2010

Я прочитал довольно много статей о замыканиях, и, как ни странно, я до сих пор не понимаю эту концепцию! Статьи объясняют, как создать замыкание, на нескольких примерах, но я не вижу смысла уделять им большое внимание, так как они в основном выглядят надуманными примерами. Я не говорю, что все они придуманы, просто те, что я нашел, выглядят надуманными, и я не вижу, как даже после их понимания я смогу их использовать. Поэтому, чтобы понять замыкания, я рассматриваю несколько реальных проблем, которые можно очень естественно решить с помощью замыканий.

Например, естественным способом объяснить рекурсию человеку может быть объяснение вычисления n !. Вполне естественно понять такую ​​проблему, как вычисление факториала числа с использованием рекурсии. Точно так же найти элемент в несортированном массиве почти просто, прочитав каждый элемент и сравнив его с соответствующим числом. Кроме того, на другом уровне выполнение объектно-ориентированного программирования также имеет смысл.

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

Ответы [ 8 ]

3 голосов
/ 19 мая 2010

Обратные вызовы являются отличным примером. Давайте возьмем JavaScript.

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

function handleClickOnReadMore(element) {
  // the user clicked on one of our 17 "request more info" buttons.
  // we'll put a whirly gif next to the clicked one so the user knows
  // what he's waiting for...
  spinner = makeSpinnerNextTo(element);

  // now get the data from the server
  performAJAXRequest('http://example.com/',
      function(response) {
        handleResponse(response);

        // this is where the closure comes in
        // the identity of the spinner did not
        // go through the response, but the handler
        // still knows it, even seconds later,
        // because the closure remembers it.
        stopSpinner(spinner);
      }
  );
}
2 голосов
/ 19 мая 2010

Мне лично очень полезна презентация Стюарта Лэнгриджа по Закрытия в JavaScript (слайды в pdf ) Там полно хороших примеров и немного юмора.

alt text

2 голосов
/ 19 мая 2010

Ну, через некоторое время, потраченное с Scala , я не могу представить себе код, работающий над некоторым списком без замыканий. Пример:

val multiplier = (i:Int) => i * 2
val list1 = List(1, 2, 3, 4, 5) map multiplier

val divider = (i:Int) => i / 2
val list2 = List(1, 2, 3, 4, 5) map divider

val map = list1 zip list2

println(map)

Вывод будет

List((2,0), (4,1), (6,1), (8,2), (10,2))

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

2 голосов
/ 19 мая 2010

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

List<Person> fList = new List<Person>();
fList.Sort((a, b) => a.Age.CompareTo(b.Age));
1 голос
/ 19 мая 2010

Хорошо, скажем, вы генерируете меню, скажем, в javascript.

var menuItems = [
   { id: 1, text: 'Home' },
   { id: 2, text: 'About' },
   { id: 3, text: 'Contact' }
];

И вы создаете их в цикле, как это:

for(var i = 0; i < menuItems.length; i++) {

    var menuItem = menuItems[i];

    var a = document.createElement('a');
    a.href = '#';
    a.innerHTML = menuItem.text;

    // assign onclick listener

    myMenu.appendChild(a);

}

Теперь, для слушателя onclick, вы можете попробовать что-то вроде этого:

a.addEventListener('click', function() {
    alert( menuItem.id );
}, false);

Но вы обнаружите, что это будет иметь каждую ссылку оповещения «3». Поскольку во время щелчка ваш код onclick выполняется, и значение menuItem вычисляется до последнего элемента, поскольку это было значение, которое было ему назначено в последний раз при последней итерации цикла for.

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

a.addEventListener('click', (function(item) {
    return function() {
        alert( item.id );
    }
})( menuItem ), false);

Так что здесь происходит? На самом деле мы создаем функцию, которая принимает item, а затем немедленно вызывает эту функцию, передавая menuItem. Так что функция «обертка» - это не то, что будет выполняться при нажатии на ссылку. Мы выполняем его немедленно и назначаем возвращенную функцию в качестве обработчика щелчков.

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

Надеюсь, что все прояснилось =)

1 голос
/ 19 мая 2010

Закрытие является одной из сильных сторон JavaScript, потому что JavaScript является лямбда-языком. Подробнее об этом можно узнать здесь:
Попытка упростить некоторые Javascript с помощью замыканий

1 голос
/ 19 мая 2010

Ну, я ежедневно использую Closures в форме оператора композиции функций и оператора карри, оба реализованы в Scheme с помощью замыканий:

Быстрая сортировка в схеме, например:

(define (qsort lst cmp)
  (if (null? lst) lst
      (let* ((pivot (car lst))
             (stacks (split-by (cdr lst) (curry cmp pivot))))
        (append (qsort (cadr stacks) cmp)
                (cons pivot
                      (qsort (car stacks) cmp))))))

Где я cmp - это двоичная функция, обычно применяемая как (cmp один два), в этом случае я каррировал ее, чтобы разделить мой стек на два, создав унитарный предикат, если хотите, операторы карри:

(define (curry f . args)
  (lambda lst (apply f (append args lst))))

(define (curryl f . args)
  (lambda lst (apply f (append lst args))))

Который соответственно карри правша и левша.

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

1 голос
/ 19 мая 2010

В C # функция может реализовывать концепцию фильтрации по близости к точке:

IEnumerable<Point> WithinRadius(this IEnumerable<Point> points, Point c, double r)
{
    return points.Where(p => (p - c).Length <= r);
}

Лямбда (замыкание) захватывает предоставленные параметры и связывает их в вычисление, которое будет выполнено через некоторое время.

...