2012-09-15 12 views
9

Ich lese über die Mathe-Grundlage hinter Haskell - ich habe gelernt, wie Schließungen verwendet werden können, um den Zustand in einer Funktion zu speichern.Wie funktionieren Verschlüsse in Haskell?

Ich frage mich, ob Haskell Schließungen erlaubt und wie sie funktionieren, weil sie keine reinen Funktionen sind?

Wenn eine Funktion ihren geschlossenen Zustand ändert, kann sie verschiedene Ausgänge auf identischen Eingängen ausgeben.

Wie ist das kein Problem in Haskell? Liegt es daran, dass Sie eine Variable nicht neu zuweisen können, nachdem Sie ihr ursprünglich einen Wert zugewiesen haben?

+6

Einfach, Sie können den geschlossenen Zustand oder andere nicht ändern :) – is7s

+1

Ähnliche Fragen: http://stackoverflow.com/questions/9419175/are-closures-a-violation-of-the-functional- Programmier-Paradigma/ – amindfv

+0

@ Is7s, aber Sie können verursachen, dass zwischen Anrufen weiter instanziiert wird, wenn es sich um nicht-atomare Daten handelt. –

Antwort

8

Die Schließung "fügt" einfach zusätzliche Variablen hinzu, so dass Sie mit ihnen nichts mehr machen können als mit "normalen", dh den Zustand nicht ändern.

Lesen Sie mehr: Closures (in Haskell)

+0

Ok danke, akzeptiere diese Antwort für die Intuition-Sprache des Einsteigers – nidoran

+0

Betrachtet Haskell die geschlossenen Variablen als Werte oder Referenzen? – CMCDragonkai

+0

Ich denke, du musst zuerst ausdrücken, was du meinst, wenn du "Referenz" sagst, da es zu einiger Verwirrung führen kann, wenn man bedenkt, dass wir uns in der reinen Haskell-Welt befinden. – Bartosz

10

Sie tatsächlich Schließungen in Haskell simulieren kann, aber nicht so, wie Sie vielleicht denken. Zunächst werde ich einen Verschluss definieren:

data Closure i o = Respond (i -> (o, Closure i o)) 

Dies definiert einen Typen, der bei jedem „Schritt“ einen Wert vom Typ nimmt i, die eine Antwort vom Typ o zu berechnen ist.

Also, lassen Sie uns einen „closure“ definieren, die leeren Eingaben und Antworten mit ganzen Zahlen akzeptiert, d.h .:

incrementer :: Closure() Int 

dieses Verhalten Schließung von Anfrage zu verlangen variieren. Ich werde es einfach halten und machen es so, dass es mit 0 zur ersten Reaktion reagiert und erhöht dann seine Antwort für jede folgende Anfrage:

incrementer = go 0 where 
    go n = Respond $ \() -> (n, go (n + 1)) 

Wir können dann die Schließung wiederholt abfragen, die ein Ergebnis ergibt und ein neuer Verschluss:

query :: i -> Closure i o -> (o, Closure i o) 
query i (Respond f) = f i 

bemerkt, dass die zweite Hälfte des oben genannten Typs ein gemeinsames Muster in Haskell ähnelt, das der State monadisch:

newtype State s a = State { runState :: s -> (a, s) } 

Es kann von Control.Monad.State importiert werden. So können wir query in diesem State Monade wickeln:

query :: i -> State (Closure i o) o 
query i = state $ \(Respond f) -> f i 

... und jetzt haben wir eine generische Art und Weise jede Schließung abzufragen, um die State Monade mit:

someQuery :: State (Closure() Int) (Int, Int) 
someQuery = do 
    n1 <- query() 
    n2 <- query() 
    return (n1, n2) 

Sagen wir es unseren Schließung passieren und sehen was passiert:

>>> evalState someQuery incrementer 
(0, 1) 

Lassen Sie uns einen anderen Verschluss schreiben, die etwas willkürliche Muster zurückgibt:

weirdClosure :: Closure() Int 
weirdClosure = Respond (\() -> (42, Respond (\() -> (666, weirdClosure)))) 

...und testen Sie es:

>>> evalState someQuery weirdClosure 
(42, 666) 

Jetzt, Verschlüsse von Hand zu schreiben ziemlich umständlich erscheint. Wäre es nicht schön, wenn wir do Notation verwenden könnten, um die Schließung zu schreiben? Nun, wir können! Wir haben nur eine Änderung unserer Verschlussart zu machen:

data Closure i o r = Done r | Respond (i -> (o, Closure i o r)) 

Jetzt können wir eine Monad Instanz definieren (von Control.Monad) für Closure i o:

instance Monad (Closure i o) where 
    return = Done 
    (Done r) >>= f = f r 
    (Respond k) >>= f = Respond $ \i -> let (o, c) = k i in (o, c >>= f) 

Und wir können eine Komfortfunktion schreiben, die dem entspricht, Wartung einer einzigen Anfrage:

answer :: (i -> o) -> Closure i o() 
answer f = Respond $ \i -> (f i, Done()) 

... die wir alle unsere alten Verschlüsse umschreiben können:

incrementer :: Closure() Int() 
incrementer = forM_ [1..] $ \n -> answer (\() -> n) 

weirdClosure :: Closure() Int r 
weirdClosure = forever $ do 
    answer (\() -> 42) 
    answer (\() -> 666) 

Jetzt brauchen Sie nur unsere Abfragefunktion ändern wir:

query :: i -> StateT (Closure i o r) (Either r) o 
query i = StateT $ \x -> case x of 
    Respond f -> Right (f i) 
    Done r -> Left r 

... und es verwenden, Abfragen zu schreiben:

someQuery :: StateT (Closure() Int()) (Either()) (Int, Int) 
someQuery = do 
    n1 <- query() 
    n2 <- query() 
    return (n1, n2) 

es jetzt testen!

>>> evalStateT someQuery incrementer 
Right (1, 2) 
>>> evalStateT someQuery weirdClosure 
Right (42, 666) 
>>> evalStateT someQuery (return()) 
Left() 

Allerdings habe ich immer noch nicht berücksichtigen, dass eine wirklich elegante Methode, also werde ich durch schamlos Aufstecken meine Proxy Typ in meinem pipes als viel allgemeiner und strukturierter Art und Weise des Schreibens Schließungen und ihre abschließen Verbraucher. Der Server Typ repräsentiert einen verallgemeinerten Verschluss und der Client repräsentiert einen generalisierten Verbraucher eines Verschlusses.

+1

Danke für die Antwort, aber das meiste ist jetzt über meinen Kopf. Ich werde darauf zurückkommen, wenn ich mehr weiß – nidoran

1

Wie andere gesagt haben, erlaubt Haskell nicht, dass der "Zustand" in einer Schließung geändert wird. Dies verhindert, dass Sie irgendetwas tun, was die Funktionsreinheit beeinträchtigen könnte.

+0

Bedeutet das, wenn ich eine Variable deklariere, die von einer Funktion geschlossen wird, wenn ich später diese Variable außerhalb dieser Funktion modifiziere und dann diese Funktion ausführe, ignoriert diese Funktion meine Mutation und gib immer noch die gleiche Ausgabe zurück, als wenn ich die geschlossene Variable nicht mutierte? – CMCDragonkai

+1

Haskell erlaubt Ihnen nicht, eine Variable später zu "modifizieren". (Oder jemals.) – MathematicalOrchid