Wie verwende ich Control.Monad.State mit Parsec?

8

Ich bin überrascht, dass ich dazu keine Informationen finden konnte. Ich muss die einzige Person sein, die Probleme damit hat.

Also, sagen wir, ich habe einen Strichzähler. Ich möchte, dass es die Anzahl der Bindestriche in der Zeichenfolge zählt und die Zeichenfolge zurückgibt. So tun, als ob ich ein Beispiel gegeben hätte, das mit der Zustandsverwaltung von Parsec nicht funktioniert. Also sollte das funktionieren:

%Vor%

Und tatsächlich, das kompiliert. Okay, also versuche ich es zu benutzen:

%Vor%

Okay, das macht Sinn. Es sollte den Status und die Zeichenfolge zurückgeben. Cool.

%Vor%

Hoppla! Aber wie hätte es jemals hoffen können, überhaupt zu arbeiten? Es gibt keine Möglichkeit, den Anfangszustand einzugeben.

Es gibt auch eine Funktion:

%Vor%

Aber es gibt einen ähnlichen Fehler.

%Vor%

Ich fühle mich, als müsste ich runState darauf laufen, oder es sollte eine Funktion geben, die das schon intern macht, aber ich kann nicht herausfinden, wohin ich von hier aus gehen soll.

Edit: Ich hätte klarer spezifizieren sollen, ich wollte nicht die Zustandsverwaltung von Parsec verwenden. Der Grund ist, dass ich das Gefühl habe, dass ich nicht möchte, dass sein Zurückverfolgen beeinflusst, was es mit dem Problem sammelt, mit dem ich mich vorbereite, es zu lösen.

Herr McCann hat jedoch herausgefunden, wie das zusammenpassen sollte und der endgültige Code würde so aussehen:

%Vor%

Vielen Dank.

    
David McHealy 29.07.2011, 17:22
quelle

3 Antworten

11

Sie haben hier tatsächlich mehrere Probleme, die beim ersten Mal relativ offensichtlich sind.

Beginnend mit dem einfachsten Beispiel: dash gibt () zurück, was anscheinend nicht das ist, was Sie wollen, da Sie die Ergebnisse sammeln. Du wolltest wahrscheinlich etwas wie dash = char '-' <* modify (+1) . (Beachten Sie, dass ich hier einen Operator von Control.Applicative verwende, weil er aufgeräumter aussieht)

Als nächstes klären Sie einen Punkt der Verwirrung: Wenn Sie die vernünftig aussehende Typ-Signatur in GHCi erhalten, notieren Sie sich den Kontext von (Control.Monad.State.Class.MonadState t Data.Functor.Identity.Identity, Num t) . Das sagt nicht, welche Dinge sind , es sagt dir, dass sie wollen müssen sein. Nichts garantiert, dass die Instanzen, nach denen es fragt, existieren und tatsächlich nicht. Identity ist keine Statusmonade!

Auf der anderen Seite haben Sie absolut recht, wenn Sie denken, dass parse keinen Sinn ergibt; Sie können es hier nicht verwenden. Betrachte seinen Typ: Stream s Identity t => Parsec s () a -> SourceName -> s -> Either ParseError a . Wie bei Monadetransformatoren üblich, ist Parsec ein Synonym für ParsecT , das auf die Identitätsmonade angewendet wird. Und während ParsecT den Benutzerstatus bereitstellt, möchten Sie ihn offenbar nicht verwenden, und ParsecT gibt nicht eine Instanz von MonadState an. Hier ist die einzige relevante Instanz: MonadState s m => MonadState s (ParsecT s' u m) . Mit anderen Worten, um einen Parser als Zustands-Monade zu behandeln, müssen Sie ParsecT auf eine andere Zustands-Monade anwenden.

Diese Art bringt uns zum nächsten Problem: Ambiguität. Sie verwenden viele Klassenmethoden und keine Typ-Signaturen, sodass Sie wahrscheinlich in Situationen geraten, in denen GHC nicht wissen kann, welchen Typ Sie tatsächlich möchten, also müssen Sie es ihm sagen.

Nun, als schnelle Lösung, lassen Sie uns zunächst ein Typ-Synonym definieren, um dem gewünschten Monad-Transformator-Stack einen Namen zu geben:

%Vor%

Geben Sie dashCounter die relevante Typ-Signatur an:

%Vor%

Und fügen Sie eine spezielle Funktion "run" hinzu:

%Vor%

Nun, in GHCi:

%Vor%

Beachten Sie auch, dass es ziemlich üblich ist, einen newtype um einen Transformer-Stack anstatt nur eines Typ-Synonyms zu verwenden. Dies kann in einigen Fällen bei Mehrdeutigkeitsproblemen helfen und vermeidet offensichtlich, dass gigantische Typ-Signaturen erhalten werden.

    
C. A. McCann 29.07.2011, 17:58
quelle
7

Wenn Sie die Benutzerstatuskomponente verwenden möchten, die Parsec als integrierte Funktion anbietet, können Sie die monadischen Funktionen getState und modifyState verwenden.

Ich habe versucht, Ihrem Beispielprogramm treu zu bleiben, obwohl die Rückgabe von dash nicht sinnvoll erscheint.

%Vor%

Beachten Sie, dass runP in der Tat Ihre Bedenken bezüglich runState anspricht.

    
Anthony 29.07.2011 17:54
quelle
4

Während diese Antworten dieses spezifische Problem aussortieren, ignorieren sie das ernstere grundlegende Problem mit einem solchen Ansatz. Ich möchte es hier für jeden anderen beschreiben, der diese Antwort betrachtet.

Es gibt einen Unterschied zwischen dem Benutzerstatus und der Verwendung des StateT-Transformators. Der interne Benutzerstatus wird beim Zurückverfolgen zurückgesetzt, StateT jedoch nicht. Betrachten Sie den folgenden Code. Wir möchten eins zu unserem Zähler hinzufügen, wenn es einen Strich gibt, und zwei, wenn es ein Plus gibt. Sie produzieren unterschiedliche Ergebnisse.

Wie zu sehen ist, liefert sowohl das Verwenden des internen Zustands als auch das Anbringen eines StateT-Transformators das korrekte Ergebnis. Letzteres geht zu Lasten der Notwendigkeit, Operationen explizit anzuheben und mit Typen viel vorsichtiger umzugehen.

%Vor%

Dies ist das Ergebnis der Ausführung von f, f 'und f' '.

%Vor%     
Matthew Pickering 27.07.2014 13:55
quelle

Tags und Links