Почему списки структуры данных выбора на функциональных языках?

45

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

Филип Хаглунд
источник
10
@gnat - это не дубликат этого вопроса. Принятый и правильный ответ на этот вопрос по существу «потому что неизменный связанный список позволяет делиться хвостами между обновленными и оригинальными списками», ответ, который указан как часть фона к этому вопросу ...
Жюль
9
Следует пояснить, что «список» (абстрактная концепция программирования) отличается от «связанного списка» (конкретной реализации этой концепции), так же как спецификация языка программирования отличается от его реализации. Вопрос "почему функциональные языки используют списки (абстрактную концепцию программирования) в качестве основной структуры данных?" тонко, но принципиально сильно отличается от вопроса "почему обычные реализации функциональных языков X, Y и Z используют связанные списки в качестве основной структуры данных?"
RM
Я scala Vector (который реализован в виде дерева) немного предпочтительнее List stackoverflow.com/questions/6928327/… На практике большинство людей все еще используют списки (из того, что я видел).
Akavall
Я провел несколько курсов по функциональному программированию в Scala и использовал много деревьев. Просто этот список проще для примера. Деревья используются для проблем с производительностью. Вы можете создавать неизменяемые деревья, повторно используя часть старых деревьев, так же, как вы добавляете элементы в неизменяемые списки.
Борхаб,

Ответы:

56

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

Список минусов - это самая простая рекурсивная структура данных произвольного размера.

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

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

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

Йорг Миттаг
источник
10
Да, векторы Clojure реализованы в виде деревьев, но следует иметь в виду, что они являются попытками сопоставления хеш-массивов , а не стандартными двоичными data Tree a = Leaf a | Branch a (Tree a) (Tree a)файлами. Это усиливает ваш аргумент "простоты".
wchargin
1
Постоянные векторы FWIW Clojure реализованы в виде (как указывает @wchargin несколько сложных) деревьев для быстрого (логарифмического с большим основанием) доступа и обновления произвольных элементов, на самом деле не для параллельной работы как таковой (неизменная часть позаботится об этом для некоторых степень). Другие языки FP делают такой же выбор (иногда с разными видами деревьев), когда им нужны оба из них (например, Haskell Sequenceили Scala Vector), но не используют деревья, когда им нужно только чтение, поскольку они могут достичь этого в истинное постоянное время (например, Haskell Vectorили F # через .Net ImmutableArray)
badcook
1
Например, pmapping над вектором в Clojure по-прежнему обращается к каждому элементу последовательно; древовидная структура вектора обычно скрыта от конечного пользователя.
badcook
5

На самом деле, эти списки являются деревьями! У вас есть узлы с двумя полями, carи cdr, которые могут содержать больше таких узлов или листьев. Единственное , что делает эти деревья в списки, является конвенция интерпретируют по cdrссылке в качестве ссылки на следующий узел в линейном списке, и carссылки в качестве значения текущего узла.

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

cmaster
источник
5
это зависит от языка. Конечно, вы можете реализовать дерево в LISP-лайках, используя cons-ячейки, но в Haskell, например, вам понадобится совершенно отдельная структура. Также обратите внимание, что большинство функциональных языков имеют гораздо больше циклических конструкций, чем хвостовая рекурсия. Например, базовые библиотеки Haskell предоставляют сгибы (слева и справа), сканирование, обходы деревьев, итерации ключей и значений карт и многие другие более специализированные структуры. Конечно, они реализованы с рекурсией за кулисами, но пользователю не нужно даже думать об этом, чтобы заставить их работать.
Жюль
@Jules Тем не менее, функциональное программирование было разработано и находилось под сильным влиянием таких языков, как LISP. Очевидно, что можно сделать всю эту итерацию списка более эффективной, используя скрытые массивы, или добавить синтаксический сахар, который скрывает природу списка, но чистое функциональное программирование может обойтись и обходится без него. Кроме того, Haskell - довольно недавний язык (на 32 года моложе LISP), который добавляет немало других идей, синтаксического сахара и т. Д. К чисто функциональному стилю. Я думаю, что судить о функциональном программировании, судя по Haskell, немного похоже на то, как судить о микшерах, судя по термомиксам ...
cmaster
2
@cmaster, в то время как Haskell моложе, ему все еще 27 лет и он влиял на многие языки (включая Python, Java и C #, чтобы назвать несколько влиятельных). Оценивать функциональное программирование с помощью LISP - это все равно, что оценивать императивное программирование с помощью ALGOL - и то, и другое прошло долгий путь, чем стать неузнаваемым ХОРОШО. Lisp, возможно, все еще актуален, но я бы не стал считать его «мейнстримом» и пропускаю многое из более позднего понимания, включая типы ML.
Maciej Piechotka
1
Вы не можете обработать односвязные хвостовые деревья рекурсивно или итеративно без явного стека, если хотите дотронуться до всего дерева, поэтому я не думаю, что хвостовая рекурсия имеет какое-либо отношение к нему.
k_g
@MaciejPiechotka Ни Python, ни Java, ни C # не могут рассматриваться как функциональные языки, они являются обязательными и добавляют несколько функций, которые вдохновлены функциональным программированием. Как только вы меняете состояние, вы твердо находитесь в области императивного, а не функционального программирования. Я согласен с вами, однако, что LISP определенно не мейнстрим.
Мастер