Ich arbeite daran, in 48 Stunden ein Schema zu schreiben (ich bin bis zu 85 Stunden) und bin zu dem Teil über das Hinzufügen von Variablen und Zuweisungen gekommen . In diesem Kapitel gibt es einen großen konzeptionellen Sprung, und ich wünschte, es wäre in zwei Schritten mit einem guten Refactoring dazwischen gemacht worden, anstatt direkt zur endgültigen Lösung zu springen. Wie auch immer…
Ich habe mit einer Reihe von verschiedenen Klassen verloren gegangen, die den gleichen Zweck zu dienen scheinen: State
, ST
, IORef
, und MVar
. Die ersten drei werden im Text erwähnt, während die letzte die bevorzugte Antwort auf viele StackOverflow-Fragen zu den ersten drei zu sein scheint. Sie alle scheinen einen Zustand zwischen aufeinanderfolgenden Anrufungen zu haben.
Was sind diese und wie unterscheiden sie sich voneinander?
Insbesondere machen diese Sätze keinen Sinn:
Stattdessen verwenden wir eine Funktion namens Status-Threads , mit der Haskell den Gesamtstatus für uns verwalten kann. Auf diese Weise können wir veränderbare Variablen wie in jeder anderen Programmiersprache behandeln und Funktionen zum Abrufen oder Festlegen von Variablen verwenden.
und
Mit dem IORef-Modul können Sie statusbehaftete Variablen innerhalb der E / A-Monade verwenden .
All dies macht die Linie type ENV = IORef [(String, IORef LispVal)]
verwirrend - warum die zweite IORef
? Was wird kaputt gehen, wenn ich type ENV = State [(String, LispVal)]
stattdessen schreibe ?
MVar
sollte das dem vorgezogen werdenSTRef
?STRef
garantiert, dass nur ein Thread es mutieren kann (und dass keine anderen Arten von E / A auftreten können) - ist das sicherlich besser, wenn ich keinen gleichzeitigen Zugriff auf den veränderlichen Status benötige?Ok, ich fange mit an
IORef
.IORef
liefert einen Wert, der in der E / A-Monade veränderbar ist. Es ist nur eine Referenz auf einige Daten, und wie bei jeder Referenz gibt es Funktionen, mit denen Sie die Daten ändern können, auf die sie verweisen. In Haskell arbeiten alle diese Funktionen inIO
. Sie können sich das wie eine Datenbank, eine Datei oder einen anderen externen Datenspeicher vorstellen. Sie können die darin enthaltenen Daten abrufen und festlegen. Dazu müssen Sie jedoch die E / A durchlaufen. Der Grund, warum IO überhaupt notwendig ist, ist, dass Haskell rein ist ; Der Compiler muss wissen, auf welche Daten die Referenz zu einem bestimmten Zeitpunkt verweist (lesen Sie den Blogpost von sigfpe "Sie hätten Monaden erfinden können" ).MVar
s sind im Grunde dasselbe wie ein IORef, abgesehen von zwei sehr wichtigen Unterschieden.MVar
ist ein Parallelitätsprimitiv und daher für den Zugriff von mehreren Threads ausgelegt. Der zweite Unterschied ist, dass anMVar
eine Box ist, die voll oder leer sein kann. Wo alsoIORef Int
immer einInt
(oder unten) ist,MVar Int
kann ein ein habenInt
oder es kann leer sein. Wenn ein Thread versucht, einen Wert aus einem leeren Thread zu lesenMVar
, wird er blockiert, bis der WertMVar
gefüllt wird (von einem anderen Thread). GrundsätzlichMVar a
entspricht an a einerIORef (Maybe a)
zusätzlichen Semantik, die für die Parallelität nützlich ist.State
ist eine Monade, die einen veränderlichen Zustand liefert, nicht unbedingt mit IO. In der Tat ist es besonders nützlich für reine Berechnungen. Wenn Sie einen Algorithmus haben, der den Status verwendet, aber nichtIO
, ist eineState
Monade oft eine elegante Lösung.Es gibt auch eine Monadentransformator-Version von State ,
StateT
. Dies wird häufig verwendet, um Programmkonfigurationsdaten oder Statusarten mit "Spielweltstatus" in Anwendungen zu speichern.ST
ist etwas etwas anderes. Die Hauptdatenstruktur inST
ist dieSTRef
, die wie eine,IORef
aber mit einer anderen Monade ist. DieST
Monade verwendet Tricks des Typsystems (die in den Dokumenten erwähnten "Status-Threads"), um sicherzustellen, dass veränderbare Daten der Monade nicht entkommen können. Das heißt, wenn Sie eine ST-Berechnung ausführen, erhalten Sie ein reines Ergebnis. Der Grund, warum ST interessant ist, ist, dass es sich um eine primitive Monade wie IO handelt, die es Berechnungen ermöglicht, Manipulationen auf niedriger Ebene an Bytearrays und Zeigern durchzuführen. Dies bedeutet, dassST
eine reine Schnittstelle bereitgestellt werden kann, während Operationen auf niedriger Ebene für veränderbare Daten verwendet werden, was bedeutet, dass dies sehr schnell ist. Aus Sicht des Programms ist es so, als würde dieST
Berechnung in einem separaten Thread mit threadlokalem Speicher ausgeführt.quelle
Andere haben die Kernaufgaben erledigt, aber um die direkte Frage zu beantworten:
Lisp ist eine funktionale Sprache mit veränderlichem Zustand und lexikalischem Umfang. Stellen Sie sich vor, Sie haben eine veränderbare Variable geschlossen. Jetzt haben Sie einen Verweis auf diese Variable, der in einer anderen Funktion herumhängt - beispielsweise (im Pseudocode im Haskell-Stil)
(printIt, setIt) = let x = 5 in (\ () -> print x, \y -> set x y)
. Sie haben jetzt zwei Funktionen - eine druckt x und eine legt ihren Wert fest. Wenn Sie bewertenprintIt
, möchten Sie die Nachschlag Namen von x in der Anfangs Umgebung , in derprintIt
festgelegt wurde, aber Sie wollen den Lookup - Wert , dass Namen in der Umgebung , in der gebunden ist ,printIt
wird genannt (nachsetIt
beliebig oft aufgerufen worden sein ).Neben den beiden IORefs gibt es Möglichkeiten, dies zu tun, aber Sie benötigen sicherlich mehr als den von Ihnen vorgeschlagenen Typ, sodass Sie die Werte, an die Namen gebunden sind, nicht lexikalisch ändern können. Google das "Funargs-Problem" für eine ganze Menge interessanter Vorgeschichte.
quelle