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

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

int count()
{
    static int x = 0;

    return x++;
}

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

17
Amaron 8 Июл 2009 в 05:30

8 ответов

Лучший ответ

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

Из Википедии

Замыкание закрывается над свободными переменными (переменными, которые не являются локальными переменными)

6
rado 8 Июл 2009 в 01:57

Нет, это не конец. Ваш пример - это просто функция, которая возвращает результат увеличения статической переменной.

Вот как будет работать закрытие:

function makeCounter( int x )
{
  return int counter() {
    return x++;
  }
}

c = makeCounter( 3 );
printf( "%d" c() ); => 4
printf( "%d" c() ); => 5
d = makeCounter( 0 );
printf( "%d" d() ); => 1
printf( "%d" c() ); => 6

Другими словами, разные вызовы makeCounter () производят разные функции с их собственными привязками переменных в их лексической среде, которую они «закрыли».

Изменить: я думаю, что такие примеры упрощают понимание замыканий, чем определения, но если вам нужно определение, я бы сказал: «Замыкание - это комбинация функции и среды. Среда содержит переменные, которые определены в функции. а также те, которые видны функции при ее создании. Эти переменные должны оставаться доступными для функции, пока функция существует ".

23
Peter Eddy 8 Июл 2009 в 03:26

Чтобы получить точное определение, я предлагаю посмотреть его запись в Википедии. Это особенно хорошо. Я просто хочу пояснить это на примере.

Предположим, этот фрагмент кода C # (который должен выполнять поиск AND в списке):

List<string> list = new List<string> { "hello world", "goodbye world" };
IEnumerable<string> filteredList = list;
var keywords = new [] { "hello", "world" };
foreach (var keyword in keywords)
    filteredList = filteredList.Where(item => item.Contains(keyword));

foreach (var s in filteredList)  // closure is called here
    Console.WriteLine(s);

Это обычная ошибка в C # - делать что-то подобное. Если вы посмотрите на лямбда-выражение внутри Where, вы увидите, что оно определяет функцию, поведение которой зависит от значения переменной на сайте ее определения. Это похоже на передачу функции самой переменной , а не значения этой переменной . Фактически, когда это закрытие вызывается, оно извлекает значение переменной keyword на тот момент. Результат этого образца очень интересен. Он выводит и «привет, мир», и «прощай, мир», чего мы не хотели. Что произошло? Как я сказал выше, функция, которую мы объявили с помощью лямбда-выражения, является замыканием над keyword переменной , поэтому происходит следующее:

filteredList = filteredList.Where(item => item.Contains(keyword))
                           .Where(item => item.Contains(keyword)); 

А во время выполнения закрытия keyword имеет значение "world", поэтому мы в основном фильтруем список пару раз с одним и тем же ключевым словом. Решение такое:

foreach (var keyword in keywords) {
    var temporaryVariable = keyword;
    filteredList = filteredList.Where(item => item.Contains(temporaryVariable));
}

Поскольку temporaryVariable привязан к телу цикла foreach, на каждой итерации это разные переменные. Фактически, каждое замыкание будет привязано к отдельной переменной (это разные экземпляры temporaryVariable на каждой итерации). На этот раз он даст правильные результаты («привет, мир»):

filteredList = filteredList.Where(item => item.Contains(temporaryVariable_1))
                           .Where(item => item.Contains(temporaryVariable_2));

В котором temporaryVariable_1 имеет значение "hello", а temporaryVariable_2 имеет значение "world" во время выполнения закрытия.

Обратите внимание, что замыкания вызвали увеличение срока жизни переменных (их жизнь должна была заканчиваться после каждой итерации цикла). Это также важный побочный эффект закрытия.

12
mmx 8 Июл 2009 в 08:45

Замыкание - это метод реализации для представления процедур / функций с локальным состоянием. Один из способов реализации замыканий описан в SICP. Я все равно изложу суть.

Все выражения, включая функции, оцениваются в среде . Среда - это последовательность фреймов . Фрейм сопоставляет имена переменных со значениями. На каждом кадре также есть указатель на окружающую среду. Функция оценивается в новой среде с кадром, содержащим привязки для ее аргументов. А теперь давайте посмотрим на следующий интересный сценарий. Представьте, что у нас есть функция с именем аккумулятор , которая при вычислении вернет другую функцию:

// This is some C like language that has first class functions and closures.
function accumulator(counter) {
    return (function() { return ++counter; });
}

Что произойдет, когда мы оценим следующую строку?

accum1 = accumulator(0);

Сначала создается новая среда, и целочисленный объект (для counter ) привязан к 0 в его первом кадре. Возвращаемое значение, являющееся новой функцией, привязано к глобальной среде. Обычно новая среда будет собираться мусором после того, как функция оценка окончена. Здесь этого не произойдет. аккумулятор1 содержит ссылку на него, так как ему требуется доступ к переменной counter . Когда вызывается аккумулятор1 , он увеличивает значение counter в среде, на которую указывает ссылка. Теперь мы можем вызвать аккумулятор1 функцию с локальным состоянием или закрытием.

Я описал несколько практических применений закрытий в своем блоге. http://vijaymathew.wordpress.com. (См. Сообщения «Опасные конструкции» и «О передаче сообщений»).

3
Vijay Mathew 14 Июл 2009 в 12:04

Ответов уже много, но я добавлю еще кого-нибудь ...

Замыкания не уникальны для функциональных языков. Они встречаются, например, в Паскале (и его семействе), в котором есть вложенные процедуры. В стандарте C их нет (пока), но у IIRC есть расширение GCC.

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

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

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

Решением этой проблемы является то, что при обращении к вложенной функции она упаковывается в «закрытие», содержащее значения переменных, которые ей понадобятся позже.

Лямбда Python - это простой пример функционального стиля ...

def parent () :
  a = "hello"
  return (lamda : a)

funcref = parent ()
print funcref ()

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

Замыкание немного похоже на неявный набор параметров.

2
Steve314 7 Сен 2010 в 02:15

Отличный вопрос! Учитывая, что один из принципов ООП в ООП заключается в том, что у объектов есть поведение, а также данные, замыкания представляют собой особый тип объектов, поскольку их самая важная цель - это их поведение. Тем не менее, что я имею в виду, когда говорю об их «поведении»?

(Многое из этого заимствовано из "Groovy in Action" Дирка Конига, потрясающей книги)

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

По словам Дирка, представьте себе конверт с листом бумаги внутри. Типичный объект будет иметь переменные и их значения, записанные на этой бумаге, но вместо этого в замыкании будет список инструкций. Допустим, в письме говорится: «Отдайте этот конверт и письмо своим друзьям».

In Groovy: Closure envelope = { person -> new Letter(person).send() }
addressBookOfFriends.each (envelope)

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

Некоторые детали: Объем: Объем закрытия - это данные и элементы, к которым можно получить доступ в нем. Возврат из замыкания: замыкания часто используют механизм обратного вызова для выполнения и возврата из самого себя. Аргументы: если замыкание должно принимать только 1 параметр, Groovy и другие языки предоставляют имя по умолчанию: «it», чтобы ускорить кодирование. Так, например, в нашем предыдущем примере:

addressBookOfFriends.each (envelope) 
is the same as:
addressBookOfFriends.each { new Letter(it).send() }

Надеюсь, это то, что вы ищете!

1
HipsterZipster 8 Июл 2009 в 02:30

Объект - это состояние плюс функция. Замыкание - это функция плюс состояние.

Функция f является замыканием, когда она закрывается (захватывается) x

1
Ahmed Khashaba 20 Апр 2015 в 08:35

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

Кроме того, вам не нужно передавать это начальное значение x, вы можете оставить его по умолчанию равным нулю внутри функционального блока. Это сделало бы более ясным, что он использует значение, к которому в противном случае у вас больше нет нормального доступа.

0
Michael H. 8 Июл 2009 в 02:35