2009-07-08 7 views
17

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

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

int count() 
{ 
    static int x = 0; 

    return x++; 
} 

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

+1

Википедия особенно хороша в определениях: http://en.wikipedia.org/wiki/Closure_%28computer_science%29 –

+6

Однако, как Википедия определение это, как правило, пишутся уродцами стресса и кристаллографическими вениками (то есть они не всегда самые простые птицы, чтобы понять). –

+1

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

ответ

6

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

От Wikipedia

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

+0

Даже если он не использует эти переменные? Другими словами, закрытие должно было бы сохранить свою собственную копию каждого доступного глобального в случае изменения глобального? – Amaron

+2

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

+0

@Amaron: Не «это собственная копия» - это та же самая копия. Если вы измените его, вы измените другой. (Опять же, не все языки программирования позволяют это, но это то, как определено ограничение) –

22

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

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

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() производят различные функции с их собственным связывание переменных в их лексической среде, что они «накрыла».

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

+0

Это действительно закрытие, но я искал точное точное определение того, что есть и не является закрытием. Пример, который я дал, должен был подразумевать что-то, что, по моему мнению, должно быть закрытием, но я знал, вероятно, не было закрытием. Тем не менее, я подниму вас на четкий и полезный пример, который не включает lisp. – Amaron

12

Для exact definition, I suggest looking at its Wikipedia entry. Это особенно хорошо. Я просто хочу пояснить это на примере.

Пусть этот 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 имеет значение «мир , «поэтому мы в основном фильтруем список пару раз с тем же ключевым словом. Решение:

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 имеет значение «привет» и temporaryVariable_2 имеет значение «мир» во время выполнения закрытия.

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

+3

Фантастический пост. Ясный, проницательный. Отлично сработано. – jason

+0

с.NET Framework v4.5.2 первый фрагмент кода только записывает мир привет. Фильтрованный список является {System.Linq.Enumerable.WhereListIterator } после первого цикла foreach в обоих фрагментах кода и разрешается во время второго цикла foreach – JnJnBoo

1

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

(Много это взято из «Groovy в действии» по Дирк Konig, который является удивительной книгой)

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

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

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() } 

Надежда это то, что вы ищете!

0

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

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

2

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

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

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

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

accum1 = accumulator(0); 

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

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

2

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

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

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

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

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

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

Python, лямбда простой пример функциональный стиль ...

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

funcref = parent() 
print funcref() 

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

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

1

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

функция F является замыканием, когда он закрывает над (захвачена) х

Смежные вопросы