2012-04-04 13 views
2

Ich las den Code für eine Toy URL Shortener. Allerdings gibt es wichtige Teile, die ich einfach nicht verstehen kann.Haskell: Yesod und Zustand

Es hat den folgenden Code:

data URLShort = URLShort { state :: AcidState URLStore } 

Zu Testzwecken habe ich so etwas wie dies in meinem eigenen app schrieb:

data MyApp = MyApp { state :: Int } 

konnte ich dann kompilieren von

main = warpDebug 3000 MyApp 
Wechsel

bis

main = warpDebug 3000 (MyApp 42) 

Und dann könnte ich tun liest des Staates in Handler von

tut
mystate <- fmap state getYesod 

von acid <- fmap state getYesod in dem Artikel inspiriert. Allerdings wusste ich nicht, wie man schreibt.

Ich habe auch versucht zu tun:

data MyApp = MyApp { state :: State Int Int } 

Aber ich habe nicht viel mit diesem bekommen.

Ich versuchte herauszufinden, wie AcidState nur funktioniert, indem Sie einige einfache ähnliche Beispiele, die seit AcidState alles im Speicher hält, sollte ich in der Lage sein, das gleiche zu tun?

Jede Art von allgemeiner Erklärung, was hier vor sich geht, und vielleicht auch, wie ich den Punkt vermisse, wäre sehr geschätzt.

Antwort

6

Der Datentyp AcidState a ist kein unveränderlicher Wert bis zum Ende; Es enthält intern Verweise auf veränderbare Daten. Was in diesem Fall in Jessod-land gespeichert ist, ist einfach ein unveränderlicher Verweis auf diese Daten. Wenn Sie den Status aktualisieren, aktualisieren Sie den Wert im Basisdatentyp nicht, sondern stattdessen den Speicher, auf den er verweist.

Jeder Wert in der Haskell-Welt ist unveränderlich. Viele Dinge außerhalb des Reiches von Haskell sind jedoch nicht unveränderlich; Wenn Sie beispielsweise putStrLn ausführen, ändert das Terminal seine Anzeige so, dass neuer Inhalt angezeigt wird. Die putStrLn Aktion selbst ist ein unveränderlicher reiner Wert, aber sie beschreibt , wie man eine Aktion ausführt, die Mutation einbezieht.

Es gibt andere Funktionen, die ebenfalls Aktionen ergeben, die Mutationen ausführen; Wenn Sie ref <- newIORef 0 tun, erhalten Sie einen Wert, der eine Aktion beschreibt, die eine veränderbare Speicherzelle erstellt. Wenn Sie dann modifyIORef ref (+1) tun, erhalten Sie einen Wert, der eine Aktion beschreibt, die den Wert in dieser Zelle um 1 erhöht. Der ref Wert ist ein reiner Wert, es ist einfach eine Referenz auf eine veränderbare Zelle. Der Code ist auch rein funktional, weil jedes Stück nur eine Aktion beschreibt; nichts ist veränderbar innerhalb des Haskell-Programms.

So implementiert AcidState seinen Status: durch Verwendung eines Systems, das den Status außerhalb der Haskell-Welt verwaltet.Dies ist nicht "so schlecht" wie in Sprachen wie C, da in Haskell die Wandlungsfähigkeit mit der Macht von Monaden gesteuert werden kann. Die Verwendung von AcidState ist vollkommen sicher und beinhaltet nicht die Verwendung von unsafePerformIO, soweit ich weiß.

Mit AcidState in diesem Fall verwenden Sie openAcidState emptyStore im IO Monade einen neuen Säure Staat zu schaffen (die Zeile ist ein Wert, der eine IO Aktion beschreibt, die einen neuen Säure Zustand öffnet). Sie verwenden createCheckpointAndClose, um den Säurestatus optional sicher auf der Festplatte zu speichern. Schließlich verwenden Sie die Funktion update', um den Inhalt eines Säurezustands zu ändern.

Um einen „kleinen Staat“ selbst zu erstellen IORef s mit (Die einfachste Form von wandelbaren Zustand, mit Ausnahme vielleicht der ST Monade), müssen Sie zunächst ein Feld wie diese zu Ihrem Gründungsdatentyp hinzufügen:

data VisitorCounter = VisitorCounter { visitorCounter :: IORef Int } 

Sie tun dann:

main = do 
    counter <- newIORef 0 
    warpDebug 3000 (VisitorCounter counter) 

In einem Handler können Sie den Zähler wie folgt ändern:

counter <- fmap visitorCounter getYesod 
modifyIORef counter (+1) 
count <- readIORef counter 
-- ... display the count or something 

Beachten Sie die Symmetrie zu AcidState.

Für eine Website-Zähler, würde ich tatsächlich empfehlen TVars anstelle von IORef s, wegen der Möglichkeit, dass mehrere Clients die Variable möglicherweise gleichzeitig ändern. Die Schnittstelle zu TVar s ist jedoch sehr ähnlich.


Folgen Frage von Frage Autor up?

Ich habe { visitorCounter :: TVar Int } in meiner Stiftung Art gelegt und der folgende Code in dem Handler:

counter <- fmap visitorCounter getYesod 
count <- readTVar counter 

Die erste Zeile stellt in Ordnung, aber der zweite wirft diesen Fehler:

Couldn't match expected type `GHandler sub0 Middleware t0' 
      with actual type `STM a0' 
In the return type of a call of `readTVar' 
In a stmt of a 'do' expression: count <- readTVar counter 

Wie könnte ich das beheben?

+0

Ich verstehe das nicht wirklich. Ich dachte, alle Daten in Haskell wären unveränderlich, sogar Monaden, da die "Do Notation" nur syntaktischer Zucker für funktionalen Code ist. Wenn diese Daten "heimlich" veränderbar sind, ist das keine Chance, dass Haskell, indem er annimmt, dass alles unveränderlich ist, keinen modifizierten Wert zurückgibt (weil angenommen wird, dass es unverändert ist). Sie scheinen zu vermuten, dass es wie "unsafePerformIO" funktioniert. Ist das wirklich so? – Clinton

+0

Ich habe meine Antwort aktualisiert, bitte kommentieren, wenn Sie noch Fragen haben. – dflemstr

+0

Danke für die aktualisierte Antwort. Folgefrage: Können Sie eine Implementierung einer trivialen Version von etwas wie "IORef" oder "TVar" anbieten, nur damit ich sehen kann, was sie tun (im Moment scheint es schwarze Magie zu sein)? Ich hätte auch gerne Informationen zur Thread-Sicherheit, aber ich denke, es wäre das Beste, wenn ich Ihre Antwort zuerst hinsichtlich der Implementierungsdetails durchlesen würde. – Clinton