Warum sind Listen die Datenstruktur der Wahl in funktionalen Sprachen?

45

Die meisten funktionalen Sprachen verwenden verknüpfte Listen als primäre unveränderliche Datenstruktur. Warum Listen und nicht zB Bäume? Bäume können auch Pfade und sogar Modelllisten wiederverwenden.

Filip Haglund
quelle
10
@gnat - Dies ist kein Duplikat dieser Frage. Die akzeptierte und korrekte Antwort auf diese Frage lautet im Wesentlichen "weil eine unveränderliche verknüpfte Liste das Teilen von Endpunkten zwischen aktualisierten und ursprünglichen Listen ermöglicht", eine Antwort, auf die als Hintergrund für diese Frage verwiesen wird ...
Jules
9
Ein Punkt der Klarstellung ist, dass sich eine "Liste" (das abstrakte Programmierkonzept) von einer "verknüpften Liste" (einer bestimmten Implementierung dieses Konzepts) unterscheidet, ähnlich wie sich die Spezifikation einer Programmiersprache von ihrer Implementierung unterscheidet. Die Frage "Warum verwenden funktionale Sprachen Listen (das abstrakte Programmierkonzept) als Hauptdatenstruktur?" unterscheidet sich subtil, aber grundlegend von der Frage "Warum verwenden die gängigen Implementierungen der Funktionssprachen X, Y und Z verknüpfte Listen als Hauptdatenstruktur?"
RM
I scala Vector (das als Baum implementiert ist) ist List stackoverflow.com/questions/6928327/… etwas vorzuziehen. In der Praxis verwenden die meisten Leute immer noch Listen (nach dem, was ich gesehen habe).
Akavall
Ich habe einige Kurse in funktionaler Programmierung in Scala absolviert und viel mit Bäumen gearbeitet. Es ist nur so, dass die Liste für Beispiele einfacher ist. Bäume werden für Leistungsprobleme verwendet. Sie können unveränderliche Bäume erstellen, indem Sie Teile älterer Bäume wiederverwenden, während Sie Elemente zu unveränderlichen Listen hinzufügen.
Borjab

Antworten:

56

Weil Listen einfacher sind als Bäume. (Sie können dies trivial daran erkennen, dass eine Liste ein entarteter Baum ist, bei dem jeder Knoten nur ein einziges untergeordnetes Element hat.)

Die Cons-Liste ist die einfachste mögliche rekursive Datenstruktur beliebiger Größe.

Guy Steele argumentierte beim Entwurf der Programmiersprache Fortress, dass für die massiv parallelen Berechnungen der Zukunft sowohl unsere Datenstrukturen als auch unser Kontrollfluss baumförmig mit mehreren Verzweigungen sein sollten, nicht linear wie sie jetzt sind. Vorläufig wurden die meisten unserer zentralen Datenstrukturbibliotheken jedoch mit sequentieller, iterativer Verarbeitung (oder Endrekursion, es ist eigentlich egal, sie sind dasselbe) und nicht mit paralleler Verarbeitung entwickelt.

Beachten Sie, dass z. B. in Clojure, dessen Datenstrukturen speziell für die parallele, verteilte, "wolkige" Welt von heute entworfen wurden, sogar Arrays (in Clojure Vektoren genannt), die wahrscheinlich "linearste" Datenstruktur von allen, tatsächlich als implementiert werden Bäume.

Kurz gesagt: Eine Cons-Liste ist die einfachste beständige rekursive Datenstruktur, und es war nicht erforderlich, einen komplizierteren "Standard" zu wählen. Andere sind natürlich als Optionen verfügbar, z. B. Haskell hat Arrays, Prioritätswarteschlangen, Maps, Heaps, Treaps, Versuche und alles, was Sie sich vorstellen können, aber die Standardeinstellung ist die einfache Liste der Nachteile.

Jörg W. Mittag
quelle
10
Ja, Clojure-Vektoren werden als Bäume implementiert - es ist jedoch zu beachten, dass es sich um Hash-Array-Mapping-Versuche handelt , nicht um Ihre Standard-Binärdateien data Tree a = Leaf a | Branch a (Tree a) (Tree a). Dies untermauert Ihr Argument der "Einfachheit".
wchargin
1
Die persistenten Vektoren von FWIW Clojure werden als (wie @wchargin zeigt, etwas komplexe) Bäume für den schnellen (logarithmischen mit einer großen Basis) Zugriff und die Aktualisierung beliebiger Elemente implementiert, nicht wirklich für den Parallelbetrieb an sich (der unveränderliche Teil kümmert sich darum für einige Grad). Andere FP-Sprachen treffen die gleiche Wahl (manchmal mit unterschiedlichen Baumarten), wenn sie beides wollen (z. B. Haskell Sequenceoder Scala Vector), aber verwenden Sie keine Bäume, wenn sie nur gelesen werden müssen, da sie dies in echter konstanter Zeit erreichen können (z. B. Haskell's Vectoroder F # via .Net's ImmutableArray)
badcook
1
ZB pmapgreift das Pingen über einen Vektor in Clojure immer noch nacheinander auf jedes Element zu; Die Baumstruktur des Vektors ist im Allgemeinen für den Endbenutzer nicht sichtbar.
Badcook
5

Eigentlich diese Listen sind Bäume! Sie haben Knoten mit zwei Feldern carund cdr, die mehrere solcher Knoten oder Blätter enthalten können. Das einzige, was diese Bäume zu Listen macht, ist die Konvention, die Verknüpfung als Verknüpfung zum nächsten Knoten in einer linearen Liste und die Verknüpfung als Wert des aktuellen Knotens zu interpretieren .cdrcar

Das heißt, ich vermute, dass die Prävalenz von verknüpften Listen in der funktionalen Programmierung mit der Prävalenz von Rekursion über Iteration zusammenhängt. Wenn Ihr einziges Schleifenkonstrukt die (End-) Rekursion ist, möchten Sie Datenstrukturen, die damit einfach zu verwenden sind. und verknüpfte Listen sind perfekt dafür.

cmaster
quelle
5
das kommt auf die sprache an. Natürlich können Sie einen Baum in LISP-ähnlichen Formaten mithilfe von Konsolenzellen implementieren, aber in Haskell benötigen Sie beispielsweise eine völlig separate Struktur. Beachten Sie auch, dass die meisten funktionalen Sprachen viel mehr Schleifenkonstrukte als die Schwanzrekursion haben. Die Haskell-Kernbibliotheken zum Beispiel bieten Faltungen (links und rechts), Scans, Überqueren von Bäumen, Iterationen über Schlüssel und Werte von Karten und viele andere Spezialstrukturen. Sicher, sie werden rekursiv hinter den Kulissen implementiert, aber der Benutzer muss nicht einmal darüber nachdenken, damit sie funktionieren.
Jules
@Jules Trotzdem wurde die funktionale Programmierung von Sprachen wie LISP entwickelt und stark beeinflusst. Offensichtlich ist es möglich, all diese Listeniterationen durch die Verwendung von Arrays unter der Haube effizienter zu gestalten oder syntaktischen Zucker hinzuzufügen, der die Natur einer Liste verbirgt, aber reine funktionale Programmierung kann und muss nicht. Außerdem ist Haskell eine ziemlich junge Sprache (32 Jahre jünger als LISP), die dem reinen funktionalen Stil eine Menge anderer Ideen, syntaktischen Zucker usw. hinzufügt. Ich denke, die Beurteilung der funktionalen Programmierung durch die Beurteilung von Haskell ist ein bisschen wie die Beurteilung von Mischern durch die Beurteilung von Thermomix ...
cmaster
2
@cmaster Während Haskell jünger ist, ist es noch 27 Jahre alt und beeinflusst viele Sprachen (einschließlich Python, Java und C #, um nur einige einflussreiche zu nennen). Das Beurteilen der funktionalen Programmierung durch LISP ist wie das Beurteilen der imperativen Programmierung durch ALGOL - beides ist längst nicht mehr erkennbar. OKAY. Lisp ist wohl immer noch relevant, aber ich würde es nicht als "Mainstream" bezeichnen und vermisse viele spätere Einsichten, einschließlich der ML-Typen.
Maciej Piechotka
1
Sie können einfach verknüpfte Bäume nicht rekursiv oder iterativ ohne expliziten Stapel verarbeiten, wenn Sie den gesamten Baum berühren möchten. Ich denke also, dass die Schwanzrekursion nichts damit zu tun hat.
k_g
@MaciejPiechotka Weder Python, Java noch C # können als funktionale Sprachen angesehen werden. Sie sind unerlässlich und fügen einige Funktionen hinzu, die von der funktionalen Programmierung inspiriert sind. Sobald Sie den Status geändert haben, befinden Sie sich fest im Bereich der imperativen, nicht der funktionalen Programmierung. Ich stimme Ihnen jedoch zu, dass LISP definitiv kein Mainstream ist.
cmaster