Чи всі функціональні мови використовують збирання сміття?


32

Чи є функціональна мова, яка дозволяє використовувати семантику стека - автоматичне детерміноване знищення в кінці області дії?


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

Мене цікавить контекст питання, що стосується отера?
mattnz

1
У функціональній мові без збору сміття я не бачу, як можливий структурний обмін незмінними структурами даних. Можливо, можна створити таку мову, але я не використовую.
dan_waterworth

Іржа має безліч функцій, які зазвичай ідентифікуються як "функціональні" (принаймні, їх зазвичай шукають у нефункціональних журналах як функціональні функції). Мені цікаво, чого цього не вистачає. За замовчуванням Immut, закриття, функція проходження, принципове перевантаження, ADT (ще немає GADT), узгодження шаблонів, все без GC. Що ще?
Ноейн

Відповіді:


10

Не те, про що я знаю, хоча я не фахівець з функціонального програмування.

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

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

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

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

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

act = terminate ((def-var "hello" ) >>>= \h ->
                 (def-var " world") >>>= \w ->
                 (use-val ((get h) ++ (get w)))
                )

Переводячи до монадійної композиції з наступним порядком виконання, кожен тег (не елемент) стає нормальною монадичною дією ...

<def-var val="hello">  --  construction
  <def-var val=" world>  --  construction
    <use-val ...>
      <terminator/>
    </use-val>  --  do nothing
  </def-val>  --  destruction
</def-val>  --  destruction

Такі правила можуть дозволити реалізацію RAII у стилі C ++. Посилання, подібні до IORef, не можуть уникнути своєї сфери застосування, з аналогічних причин, чому нормальні IORefs не можуть уникнути монади - правила асоціативного складу не дають змоги уникнути посилання.

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

Отже, конструкція може, наприклад, відкрити файл, який знищує закриття. Конструкція може відкрити розетку, де руйнування закривається. В основному, як і в C ++, змінні стають менеджерами ресурсів. Але на відміну від C ++, немає об'єктів, виділених купу, які неможливо автоматично знищити.

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

Тому цього досягається - відокремити управління ресурсами в стилі RAII від управління пам'яттю, принаймні у випадку, коли RAII базується на простому вкладенні. Вам все ще потрібен сміттєзбірник для управління пам’яттю, але ви отримуєте безпечне та своєчасне автоматичне детерміноване очищення інших ресурсів.


Я не розумію, чому GC необхідний у всіх функціональних мовах. якщо у вас є рамка RAII стилю C ++, компілятор також може використовувати цей механізм. Спільні значення не є проблемою для фреймворків RAII (див. C ++ shared_ptr<>), ви все одно зберігаєте детерміновані руйнування. Єдине, що є складним для RAII, - це циклічні посилання; RAII працює чисто, якщо графік власності - це спрямований ациклічний графік.
MSalters

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

@comingstorm - C ++ має лямбда (станом на C ++ 11), але не має стандартного сміттєзбірника. Лямбди теж переносять своє оточення - і елементи цього середовища можуть передаватися посиланням, а також можливістю передачі покажчиків за значенням. Але, як я писав у своєму другому абзаці, C ++ допускає можливість звисання покажчиків - відповідальність за програмісти (а не за компілятори чи середовища виконання) забезпечують, щоб цього ніколи не відбулося.
Стів314

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

Я заперечую, що керування пам'яттю вручну означає, що ви не маєте такої ж свободи користуватися закриттями C ++ 11, як і закриття Lisp або Haskell. (Мені насправді дуже цікаво зрозуміти деталі цього компромісу, оскільки я хотів би написати мову функціональної системи програмування ...)
штурм

3

Якщо ви вважаєте, що C ++ є функціональною мовою (у ній лямбда), то це приклад мови, яка не використовує сміття.


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

@mattnz Тоді я думаю, що відповідь не застосовується. Я не впевнений, що відбувається на інших мовах (як, наприклад, haskel)
BЈовиЈ

9
Скажіть, що C ++ є функціональним, як сказати, що Perl є об'єктно-орієнтованим ...
Динамічний

Принаймні компілятори c ++ можуть перевірити наявність побічних ефектів. (через const)
tp1

@ tp1 - (1) Я сподіваюсь, що це не вдасться відрегулювати мову, яка найкраща, і (2) це не зовсім так. По-перше, дійсно важливими наслідками є в основному введення-виведення. По-друге, const навіть не впливає на пам'ять, що змінюється, не блокує їх. Навіть якщо ви припускаєте, що немає можливості підривати типову систему (як правило, розумно в C ++), тут виникає проблема логічної стійкості та "змінного" ключового слова C ++. По суті, ви все ще можете мати мутації, незважаючи на const. Від вас очікується, що результат все ще буде "логічно" однаковим, але не обов'язково однаковим.
Стів314

2

Я мушу сказати, що питання трохи не визначене, оскільки передбачає, що існує стандартна колекція "функціональних мов". Практично кожна мова програмування підтримує деяку кількість функціонального програмування. І майже кожна мова програмування підтримує деяку кількість імперативного програмування. Де можна провести межу, щоб сказати, що є функціональною мовою, а яка - імперативною мовою, крім керованих культурними забобонами та популярною догмою?

Кращим способом сформулювати питання було б: "чи можна підтримувати функціональне програмування у виділеній пам'яті стеку". Відповідь, як уже було сказано, дуже складна. Функціональний стиль програмування сприяє розподілу рекурсивних структур даних за власним бажанням, що вимагає накопичувальної пам’яті (чи збирається сміття, чи відраховується посилання). Однак існує досить складна техніка аналізу компілятора, яка називається аналізом пам'яті на основі регіону, за допомогою якого компілятор може розділити купу на великі блоки, які можуть бути розподілені і розміщені автоматично, таким чином, як розподіл стеків. На сторінці Вікіпедії перераховані різні способи реалізації методики як для "функціональних", так і для "імперативних" мов.


1
Підрахунок посилань на ІМО - це збирання сміття, а наявність купи не означає, що це єдині варіанти. C mallocs і mfrees, використовуючи купу, але не має (стандартного) сміттєзбірника і підраховує лише посилання, якщо ви пишете код для цього. C ++ майже такий самий - у нього є (як стандартно, в C ++ 11) смарт-покажчики із вбудованим підрахунком посилань, але ви все одно можете робити вручну нові та видаляти, якщо вам це справді потрібно.
Стів314

Поширеною причиною твердження, що підрахунок посилань не є сміттям, є те, що він не може зібрати еталонні цикли. Це звичайно стосується простих реалізацій (можливо, включаючи смарт-покажчики на C ++ - я не перевіряв), але це не завжди так. Принаймні одна віртуальна машина Java (IBM, IIRC) використовувала підрахунок посилань як основу для збирання сміття.
Стів314
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.