Sprachen mit anwendbarer Reihenfolge unterstützen keine Schreibvorgänge für konstante Zeitarrays? Wenn nicht, warum nicht?

7

Ich lese Steven Skienas "The Algorithm Design Manual" und in einer seiner Kriegsgeschichten auf Seite 155 heißt es:

Effizienz ist in Mathematica eine große Herausforderung, da das Berechnungsmodell anwendbar ist (es unterstützt keine zeitkonstanten Schreiboperationen für Arrays) und der Interpretationsaufwand (im Gegensatz zur Kompilierung).

Ich habe SICP bereits gelesen, daher bin ich mit dem Unterschied zwischen Sprachen mit anwendbarer und normaler Ordnung vertraut (dh, Sprachen mit normaler Ordnung verzögern die Bewertung von Prozedurargumenten, bis sie benötigt werden, während Sprachen mit anwendbarer Ordnung sie bewerten, sobald die Prozedur heißt). Aber Skienas obiger Satz scheint die Idee von Sprachen der anwendbaren Ordnung mit der Idee von Array-Schreibvorgängen zu verbinden, die schlechter als die konstante Zeit sind. Ich erinnere mich nicht, dass Abelson und Sussman dies in ihrem Text erwähnt haben, daher war dies eine Überraschung.

Wenn ja, was sind die Gründe, warum Sprachen mit anwendbarer Reihenfolge nicht in konstanter Zeit in Arrays schreiben? Warum sollte die Reihenfolge der Auswertung bei der Bestimmung der Array-Schreibzeit eine Rolle spielen?

Ich bin auch neugierig , was die Big-O - Schreibleistung ist in diesem Fall, aber ich glaube , dass auf der Sprachimplementierung abhängt, so dass ich diese Frage überspringen würde , es wäre denn es eine endgültige Antwort ist.

Richie Thomas
quelle

Antworten:

11

Skiena sagte nicht „applicative Ordnung “, nur „applicative“. Dies wird manchmal als "rein funktional" oder als eine Sprache verwendet, die über die Anwendung von Funktionen im Gegensatz zur Ausführung von Zustandsmanipulationsbefehlen ausgewertet wird. Mathematica ist (zumindest heutzutage) nicht rein funktional, aber es scheint, dass es einen rein funktionalen Programmierstil stark fördert.

In einer rein funktionalen Sprache wie Haskell wenden Sie Funktionen auf Werte an und erzeugen neue Werte wie in der Mathematik. Wie in der Mathematik sind Variablen in einer solchen Sprache nur Namen für Werte. Wenn Sie sagen "lassenx Sein 1", dann x und 1sind überall austauschbar. Es macht keinen Sinn mehr, von "Mutieren" zu sprechen.x sein 2 als es zu sagen zu setzen 1 sein 2. Für ein Array bedeutet dies, dass Sie zum "Aktualisieren" eines Arrays ein neues Array mit den Änderungen erstellen. Das Erstellen einer vollständigen Kopie eines Arrays mit Ausnahme des einen Eintrags, den Sie ändern, ist natürlich teuer. Dies ist mit ziemlicher Sicherheit das Thema, auf das sich Skiena bezieht.

Meines Wissens gibt es keine völlig zufriedenstellende Antwort darauf. Das heißt, es gibt keine rein funktionale Datenstruktur, die eine konstante Zeitsuche und Aktualisierung aller Versionen ermöglicht . Sie können (extern) reine Datenstrukturen erstellen, die eine konstante Zeitsuche und Aktualisierung auf den neuesten Stand bringenVersionen, aber der Zugriff auf ältere Versionen des Arrays wird langsamer. Sie können Monaden oder Eindeutigkeitstypen verwenden, um die Verwendung von Arrays zu erzwingen, die es dem Compiler ermöglichen, direkte Aktualisierungen zu verwenden. Dies ist jedoch gleichbedeutend mit einem imperativen Ansatz (wenn auch auf kontrollierte und geschlossene Weise). In der Praxis verwenden Programmierer in rein funktionalen Sprachen Arrays nicht annähernd so häufig wie zwingende Programmierer, und wenn sie Arrays verwenden, verwenden sie in der Regel Massenoperationen. Wenn Sie ohnehin jedes Element eines Arrays berühren, sind die Kosten für das Kopieren des Arrays pro Element konstant.

Während es definitiv Zeiten gibt, in denen die Einschränkungen der rein funktionalen Programmierung eine effiziente Implementierung nicht offensichtlich machen, geht es meistens nur darum, andere Entwurfs- und Implementierungstechniken anzuwenden, als sie in imperativen Sprachen typisch / praktisch sind. Reinheit bietet auch Garantien, die viele Designs, Optimierungen und manchmal ganze Technologien praktisch machen. Zum einen sind die meisten rein funktionalen Datenstrukturen so konzipiert, dass sie so viel wie möglich gemeinsam nutzen. Zum zweiten ist die Fusion eine wichtige Technik zur Verbesserung der Leistung von Massenoperationen, bei der mehrere Massenoperationen zu einer kombiniert werden, um Zwischenprodukte zu vermeiden. Für das letzte,

Derek Elkins verließ SE
quelle
3
Auch eine Baumdatenstruktur mit einem großen Fan-Out ist "praktisch konstant". Zum Beispiel ist ein 64-jähriger Baum mit 4 Milliarden Elementen höchstens 7 Hopfen von der Wurzel bis zum Blatt. Das sind 7 Zeigersuchen im Gegensatz zu 1 für ein Array. Nicht so schlecht. Wie Rich Hickey, der Designer von Clojure, gern sagt: Clojures Vektoren sind O (log_32 n), und die 32 ist wichtig, da für viele praktische Problemgrößen Konstanten eine Rolle spielen. O (log_X n) für X> 30 ist selbst für sehr große n ~ Milliarden verdammt nahe an O (1).
Jörg W Mittag
4
@ JörgWMittag Und selbst in einer imperativen Welt ist der O (1) -Array-Zugriff eine kleine Lüge, da die Hardware O (log n) -Gatter durchlaufen muss, um auf eine Speicherzelle zuzugreifen. Das RAM-Kostenmodell postuliert bequemerweise den O (1) -Zugriff, und wir akzeptieren dies nur, weil es nahe genug zum Üben ist (wo wir sowieso nicht über zig Millionen Bytes Speicher verfügen).
Chi
@ JörgWMittag: Vielleicht verstehe ich das falsch, aber - in einer rein funktionalen Baumstruktur, wie Sie sie beschreiben, wäre ein Get 7-mal so teuer, aber ein Update wäre 7x64-mal so teuer, und sogar die Kosten für die Zuweisung aller neuen werden reduziert Arrays. Wenn das Verhältnis von Lesevorgängen zu Schreibvorgängen nicht sehr hoch ist, ist dies kein trivialer Kompromiss.
Ruakh