Reine funktionale Programmiersprachen erlauben keine veränderlichen Daten, aber einige Berechnungen werden eher natürlich / intuitiv imperativ ausgedrückt - oder eine imperative Version eines Algorithmus kann effizienter sein. Ich bin mir bewusst, dass die meisten funktionalen Sprachen nicht rein sind und dass Sie Variablen zuweisen / neu zuweisen und imperative Dinge tun können, aber generell davon abraten.
Meine Frage ist: Warum darf der lokale Zustand nicht in lokalen Variablen manipuliert werden, sondern erfordert, dass Funktionen nur auf ihre eigenen lokalen Variablen und globalen Konstanten (oder nur in einem äußeren Bereich definierte Konstanten) zugreifen können? Auf diese Weise behalten alle Funktionen referenzielle Transparenz bei (sie geben immer denselben Rückgabewert bei denselben Argumenten an), aber innerhalb einer Funktion kann eine Berechnung imperativ ausgedrückt werden (wie zB eine while-Schleife).
IO und so könnte man immer noch auf normale funktionale Art und Weise erreichen - durch Monaden oder Umgehen eines "Welt" - oder "Universum" -Token.
Die kurze Antwort lautet: Es gibt Systeme, die erlauben, was Sie wollen. Zum Beispiel können Sie es mit der ST
monad in Haskell tun (wie in den Kommentaren verwiesen).
Der Ansatz ST
monad stammt von Haskells Control.Monad.ST
. Code, der in der ST
-Monade geschrieben ist, kann Referenzen ( STRef
) verwenden, wo es praktisch ist. Der nette Teil ist, dass Sie sogar die Ergebnisse von ST
monad im reinen Code verwenden können, da es im Wesentlichen in sich abgeschlossen ist (das ist im Grunde das, was Sie in der Frage wollten).
Der Nachweis dieser in sich geschlossenen Eigenschaft erfolgt durch das Typsystem. Die ST
-Monade enthält einen state-thread-Parameter, der normalerweise mit einer Typvariablen s
angegeben wird. Wenn Sie eine solche Berechnung haben, haben Sie ein monadisches Ergebnis mit einem Typ wie:
Um dies zu einem reinen Ergebnis zu machen, müssen Sie
verwenden %Vor% Sie können diesen Typ wie folgt lesen: Geben Sie mir eine Berechnung, bei der der Parameter s
type keine Rolle spielt, und ich kann Ihnen das Ergebnis der Berechnung ohne das ST
baggage zurückgeben. Dies verhindert grundsätzlich, dass die veränderbaren Variablen ST
entweichen, da sie die s
mit sich führen würden, die vom Typsystem erfasst würden.
Dies kann gut für reine Strukturen verwendet werden, die mit zugrunde liegenden veränderbaren Strukturen implementiert sind (wie das Vektorpaket <) / a>). Man kann die Unveränderlichkeit für eine begrenzte Zeit ablegen, um etwas zu tun, das das zugrundeliegende Array an Ort und Stelle verändert. Zum Beispiel könnte man das unveränderliche Vector
mit einem Paket mit unreinen Algorithmen kombinieren, um es am besten zu behalten der Leistungsmerkmale der vor Ort Sortieralgorithmen und immer noch Reinheit erhalten.
In diesem Fall würde es etwa so aussehen:
%Vor% Die Funktionen thaw
und freeze
sind lineares Kopieren, aber dies wird die gesamte O (n lg n) Laufzeit nicht stören. Sie können sogar unsafeFreeze
verwenden, um einen weiteren linearen Durchlauf zu vermeiden, da der veränderbare Vektor nicht erneut verwendet wird.
Meine Frage ist, warum der lokale Zustand nicht in lokalen Variablen manipuliert werden darf, sondern dass Funktionen nur auf ihre eigenen lokalen und globalen Konstanten (oder nur in einem äußeren Bereich definierte Konstanten) zugreifen können?
Gute Frage. Ich denke, die Antwort ist, dass wandelbare Locals von begrenztem praktischen Wert sind, aber veränderliche Heap-zugeteilte Datenstrukturen (hauptsächlich Arrays) sind enorm wertvoll und bilden das Rückgrat vieler wichtiger Sammlungen, einschließlich effizienter Stapel, Warteschlangen, Mengen und Wörterbücher. Eine Beschränkung der Mutation auf Einheimische würde also keine ansonsten rein funktionale Sprache für einen der wichtigen Vorteile einer Mutation geben.
In einer verwandten Anmerkung bieten sequentielle Prozesse, die rein funktionale Datenstrukturen austauschen, viele Vorteile beider Welten, da die sequentiellen Prozesse die Mutation intern verwenden können, z. Veränderbare Nachrichtenwarteschlangen sind ~ 10x schneller als alle rein funktionalen Warteschlangen. Zum Beispiel ist dies idiomatisch in F #, wo der Code inMailboxProcessor
veränderbare Datenstrukturen verwendet, aber die zwischen ihnen kommunizierten Nachrichten unveränderlich sind.
Das Sortieren ist eine gute Fallstudie in diesem Zusammenhang. Sedgewicks Quicksort in C ist kurz und einfach und hunderte Male schneller als die schnellste rein funktionale Art in jeder Sprache. Der Grund dafür ist, dass Quicksort das Array an Ort und Stelle mutiert. Veränderliche Einheimische würden nicht helfen. Die gleiche Geschichte für die meisten Graphalgorithmen.
Tags und Links functional-programming state purely-functional imperative