Проблеми із застосуванням закривань у нефункціональних налаштуваннях


18

У мовах програмування закриття - популярна і часто бажана особливість. Вікіпедія говорить (моє наголос):

В інформатиці закриття (...) - це функція разом із референтним середовищем для нелокальних змінних цієї функції. Закриття дозволяє функції отримувати доступ до змінних за межами її безпосередньої лексичної області.

Отже, закриття - це, по суті, значення (анонімне?), Яке може використовувати змінні за межами своєї власної області. На мій досвід, це означає, що він може отримати доступ до змінних, які знаходяться в області застосування в його точці визначення.

На практиці, здається, концепція розходяться, хоча б поза функціональним програмуванням. Різні мови реалізують різну семантику, навіть, здається, існують війни опінонів. Багато програмістів, здається, не знають, що таке закриття, розглядаючи їх як трохи більше, ніж анонімні функції.

Також, мабуть, існують великі перешкоди при здійсненні закриття. Найпомітніший, Java 7 повинен був включати їх, але ця функція була повернута до майбутнього випуску.

Чому закриття так важко (зрозуміти і) реалізувати? Це занадто широке і розпливчасте запитання, тому дозвольте мені зосередитися на цих взаємопов’язаних питаннях:

  • Чи є проблеми із вираженням закриттів у загальних семантичних формалізмах (малий крок, великий крок, ...)?
  • Чи існуючі системи типу не підходять для закриття і їх не можна легко поширити?
  • Чи проблематично привести закриття у відповідність до традиційного перекладу процедур на основі стеку?

Зауважимо, що питання стосується здебільшого процедурних, об'єктно-орієнтованих та сценарійних мов загалом. Наскільки мені відомо, функціональні мови не мають жодних проблем.


Гарне питання. Закриття були реалізовані в Scala, і Мартін Одерський написав компілятор Java 1.5, тому незрозуміло, чому вони відсутні в Java 7. C # має їх. (Спробую написати кращу відповідь пізніше.)
Дейв Кларк

4
Нечисті функціональні мови, такі як Lisp та ML, добре закривають закриття, тому не може бути суттєвої семантичної причини для них проблематичною.
Жил "ТАК - перестань бути злим"

Я включив цей предмет, тому що я намагаюся уявити, як може виглядати семантичний дрібний крок для закриття. Цілком може бути, що закриття саме по собі не є проблемою, але включити їх у мову, яка не призначена для них на увазі, важко.
Рафаель

1
Погляньте на pdfs.semanticscholar.org/73a2/… - автори Lua зробили це дуже розумним шляхом, а також обговорили загальні проблеми впровадження закриття
Bulat

Відповіді:


10

Чи можу я направити вас на сторінку Вікіпедії про проблему Фунарга ? Принаймні, таким чином користувачі-компілятори посилалися на проблему впровадження закриття.

Отже, закриття - це, по суті, значення (анонімне?), Яке може використовувати змінні за межами своєї власної області. На мій досвід, це означає, що він може отримати доступ до змінних, які знаходяться в області застосування в його точці визначення.

Хоча це визначення має сенс, воно не допомагає описати проблему реалізації функцій першого класу в традиційній мові, що базується на стеці виконання. Що стосується питань реалізації, функції першого класу можна приблизно розділити на два класи:

  • Локальні змінні у функціях ніколи не використовуються після повернення функції.
  • Локальні змінні можна використовувати після повернення функції.

Перший випадок (фунарги вниз) не так важко реалізувати, і його можна знайти навіть на старих процедурних мовах, таких як Algol, C і Pascal. C тип спідниці питання, оскільки він не дозволяє вкладені функції, але Algol і Pascal роблять необхідні бухгалтерії, щоб внутрішні функції могли посилатися на змінні стека зовнішньої функції.

Другий випадок (вгору фунарги), з іншого боку, вимагає збереження записів активації поза стеком, у купі. Це означає, що дуже легко просочувати ресурси пам'яті, якщо мовний час роботи не включає сміттєзбірник. У той час, як сьогодні майже все зібрано сміття, потрібне одне все-таки важливе дизайнерське рішення і було тим більше деякий час тому.


Що стосується конкретного прикладу Java, якщо я правильно пам’ятаю, головне питання полягало не в тому, щоб реалізувати закриття, а як ввести їх до мови таким чином, що не буде зайвим з існуючими функціями (як-от анонімні внутрішні класи) та які не стикалися з існуючими функціями (як-от перевірені винятки - проблема, яку неможливо вирішити і про яку більшість людей не думає спочатку).

Я також можу подумати про інші речі, які роблять функції першого класу менш тривіальними для реалізації, наприклад, вирішити, що робити з "магічними" змінними, такими як ця , self або super, і як взаємодіяти з існуючими операторами потоку управління, такими як перерва та повернення. (хочемо ми дозволити нелокальні повернення чи ні?). Зрештою, недавня популярність першокласних функцій, схоже, свідчить про те, що мови, які їх не мають, здебільшого роблять це з історичних причин або через якесь важливе дизайнерське рішення на початку.


1
Чи знаєте ви які-небудь мови, які відрізняють випадки зростання та зменшення? У мовах .NET загальний метод, який очікував отримання лише вниз функції, може отримати структуру загального типу разом з делегатом, який отримає таку структуру як byref (у C # - " refпараметр"). Якщо абонент інкапсулює всі змінні, що цікавлять структуру, делегат може бути повністю статичним, уникаючи будь-якої необхідності в купі розподілу. Компілятори не пропонують жодної приємної синтаксичної допомоги для таких конструкцій, але Framework може їх підтримувати.
supercat

2
@supercat: У іржі є кілька типів закриття, які дозволяють виконувати на час компіляції, якщо для внутрішньої функції потрібно буде використовувати купу. Однак це не означає, що впровадження не може намагатися уникнути розподілу купи, не змушуючи вас дбати про всі ці додаткові типи. Компілятор може спробувати зробити висновок про функцію життя або він може використовувати перевірки виконання, щоб ліниво зберегти змінні в купу лише тоді, коли це суворо необхідно (ознайомтеся з розділом "Лексичний обсяг" статті " Еволюція Луа" для деталей)
hugomg

5

Ми можемо подивитися, як реалізуються закриття в C #. Масштаб перетворень, який виконує компілятор C #, дає зрозуміти, що їх спосіб реалізації закриття - це досить велика робота. Можливо, існують простіші способи впровадження закриття, але я думаю, що команда компілятора C # знає про це.

Розглянемо наступний псевдо-C # (я вирізав трохи C # конкретних речей):

int x = 1;
function f = function() { x++; };
for (int i = 1; i < 10; i++) {
    f();
}
print x; // Should print 9

Компілятор перетворює це на щось подібне:

class FunctionStuff {
   int x;
   void theFunction() {
       x++;
   }
}

FunctionStuff theClosureObject = new FunctionStuff();
theClosureObject.x = 1;
for (int i = 1; i < 10; i++) {
    theClosureObject.theFunction();
}
print theClosureObject.x; // Should print 9

(насправді змінна f все одно буде створена, де f - "делегат" (= покажчик функції), але цей делегат все ще пов'язаний з об'єктом theClosureObject - я залишив цю частину для наочності для тих, хто не знайомий з C #)

Ця трансформація є досить масовою і хитрою: розгляньте питання про закриття всередині закриттів та взаємодію закриттів з іншими особливостями мови C #. Я можу уявити, що ця функція була відсунута назад для Java, оскільки у Java 7 вже є досить багато нових функцій.


Я бачу, куди це йде; маючи багаторазове закриття та доступ до основного діапазону, однакова змінна буде безладною.
Рафаель

Якщо чесно, це більше пов'язано з використанням існуючої структури ОО для впровадження закриттів, ніж будь-якої реальної проблеми з ними. Інші мови просто розподіляють змінні в окремій, без методів, структурі, а потім дозволяють множинним закриттям ділитися ними, якщо вони хочуть.
hugomg

@Raphael: як ви ставитесь до закриттів всередині закриття? Зачекайте, дозвольте додати це.
Алекс десять Бринк

5

Щоб відповісти на частину вашого запитання. Формалізм, описаний Моррісеттом та Харпером, охоплює семантику великих та малих ступенів поліморфних мов вищого порядку, що містять закриття. Перед цим є документи, які надають види семантики, яку ви шукаєте. Подивіться, наприклад, на машині SECD . Додавання змінних посилань чи змінних місцевих жителів до цієї семантики є простим. Я не бачу, що в наданні такої семантики є якісь технічні проблеми.


Дякую за довідку! Це, здається, не є легким для читання, але цього, мабуть, можна очікувати з семантичної роботи.
Рафаель

1
@Raphael: Напевно, є більш прості. Я спробую щось знайти і повернутися до вас. У будь-якому випадку, на малюнку 8 є семантика, яку ви шукаєте.
Дейв Кларк

Можливо, ви можете дати приблизний огляд респ. центральні ідеї у вашій відповіді?
Рафаель

2
@Raphael. Можливо, я міг би віднести вас до своїх конспектів лекцій, які я використовую для курсу мов програмування, який дає вам швидке ознайомлення. Будь ласка, знайдіть роздаткові матеріали 8 та 9.
Удай Редді,

1
Це посилання з’являється або мертвим, або позаду невидимої автентифікації ( cs.cmu.edu/afs/cs/user/rwh/public/www/home/papers/gcpoly/tr.pdf ). Мені 403 заборонено.
Бен Флетчер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.