Все ли функциональные языки используют сборку мусора?


32

Существует ли функциональный язык, который позволяет использовать семантику стека - автоматическое детерминированное уничтожение в конце области?


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

Меня интересует содержание вопроса, что общего с отером?
Mattnz

1
На функциональном языке без сборки мусора я не вижу возможности структурного разделения неизменяемых структур данных. Может быть возможно создать такой язык, но я бы не использовал его.
dan_waterworth

В Rust есть много функций, которые обычно обозначают как «функциональные» (по крайней мере, они обычно нужны в не функциональных языках как функциональные возможности). Мне любопытно, чего не хватает. По умолчанию, замыкания, передача функций, принципиальная перегрузка, ADT (пока нет GADT), сопоставление с образцом, все без GC. Что-то еще?
Noein

Ответы:


10

Не то, чтобы я знал, хотя я не эксперт по функциональному программированию.

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

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

Для монадического кода есть другое решение, которое (на самом деле или почти) тоже монадическое, и может дать своего рода автоматически детерминированно разрушенный IORef. Принцип состоит в том, чтобы определить «вложенные» действия. При объединении (с использованием ассоциативного оператора) они определяют поток управления вложением - я думаю, «XML-элемент», причем крайние левые значения обеспечивают внешнюю пару начала и конца тега. Эти «теги XML» просто определяют порядок монадических действий на другом уровне абстракции.

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

Многие из этих специальных действий будут иметь нулевой шаг «конечный тег» и будут приравнивать шаг «начальный тег» к некоторому простому монадическому действию. Но некоторые будут представлять объявления переменных. Они будут представлять конструктор с тегом begin, а деструктор с тегом end. Таким образом, вы получите что-то вроде ...

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

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

РЕДАКТИРОВАТЬ - я почти забыл сказать - есть определенная область, я не уверен в этом здесь. В принципе, важно убедиться, что внешняя переменная не может ссылаться на внутреннюю, поэтому должны быть ограничения, которые вы можете делать с этими IORef-подобными ссылками. Опять же, я не проработал все детали.

Следовательно, конструкция может, например, открыть файл, уничтожение которого завершается. Конструкция может открыть розетку, разрушение которой закрывается. По сути, как и в C ++, переменные становятся менеджерами ресурсов. Но в отличие от C ++, нет объектов, выделенных кучей, которые не могут быть автоматически уничтожены.

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

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


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

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

@comingstorm - в C ++ есть лямбды (по состоянию на C ++ 11), но нет стандартного сборщика мусора. Лямбды также переносят свое окружение в замкнутом пространстве - и элементы в этом окружении могут передаваться по ссылке, а также возможность передачи указателей по значению. Но, как я писал во втором абзаце, C ++ допускает возможность висячих указателей - ответственность за то, чтобы этого никогда не произошло, лежит на программистах (а не на компиляторах или средах выполнения).
Steve314

@MSalters - затраты на обеспечение невозможности создания эталонных циклов. Поэтому по крайней мере нетривиально делать язык ответственным за это ограничение. Присвоение указателю, вероятно, становится операцией не постоянного времени. Хотя это может быть лучшим вариантом в некоторых случаях. Сборка мусора позволяет избежать этой проблемы с разными затратами. Привлечение программиста к ответственности - это другое. Нет веских причин, почему в императивных, но не функциональных языках, висячие указатели должны быть в порядке, но я все еще не рекомендую писать pointer-dangling-Haskell.
Steve314

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

3

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


8
Что если вы не считаете C ++ функциональным языком (ИМХО это не так, хотя вы можете написать с ним функциональную программу, вы также можете написать несколько чрезвычайно функциональных (функционирующих ....) программ на нем)
Mattnz

@mattnz Тогда я думаю, что ответ не применяется. Я не уверен, что происходит на других языках (например, на Haskel)
B --овић

9
Сказать, что C ++ является функциональным, все равно, что сказать, что Perl является объектно-ориентированным ...
Dynamic

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

@ tp1 - (1) Я надеюсь, что это не скажется на том, чей язык лучше, и (2) это не совсем так. Во-первых, действительно важными эффектами являются в основном ввод / вывод. Во-вторых, даже для воздействия на изменчивую память const не блокирует их. Даже если вы предполагаете, что нет возможности подорвать систему типов (как правило, разумно в C ++), существует проблема логической константности и «изменяемого» ключевого слова C ++. В принципе, у вас все еще могут быть мутации, несмотря на const. От вас ожидают, что результат будет «логически» тем же, но не обязательно таким же представлением.
Steve314

2

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

Лучший способ сформулировать вопрос: «возможно ли поддерживать функциональное программирование в выделенной памяти стека». Ответ, как уже упоминалось, очень сложный. Стиль функционального программирования способствует выделению рекурсивных структур данных по желанию, для чего требуется куча памяти (будь то сборка мусора или подсчет ссылок). Однако существует довольно сложная методика анализа компилятора, называемая анализом памяти на основе областей, с помощью которой компилятор может разделять кучу на большие блоки, которые можно распределять и освобождать автоматически, аналогично распределению в стеке. На странице Википедии перечислены различные реализации этой техники как для «функциональных», так и для «императивных» языков.


1
Подсчет ссылок IMO - это сборка мусора, и наличие кучи не означает, что в любом случае это единственные варианты. C mallocs и mfrees используют кучу, но не имеют (стандартного) сборщика мусора и подсчитывают только ссылки, если вы пишете код для этого. C ++ почти такой же - он имеет (как стандарт, в C ++ 11) умные указатели со встроенным подсчетом ссылок, но вы все равно можете делать новое вручную и удалять, если вам действительно нужно.
Steve314

Распространенная причина утверждать, что подсчет ссылок не является сборкой мусора, заключается в том, что он не может собирать циклы ссылок. Это, безусловно, относится к простым реализациям (возможно, включая интеллектуальные указатели C ++ - я не проверял), но это не всегда так. По крайней мере одна виртуальная машина Java (IBM, IIRC) использовала подсчет ссылок в качестве основы для сборки мусора.
Steve314
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.