2016-08-07 12 views
5

In this answer findet man die Aussage, dass es nicht sehr schwierig sein würde, eine Read Instanz für den Datentyp Tree zu implementieren, wobei das eigentliche Parsing bereits abgeschlossen ist.Wie implementiert man eine Read-Instanz mit dem eigentlichen Parsing bereits abgeschlossen, in Haskell?

Allerdings bin ich eine harte Zeit zu verstehen, wie eine solche Funktion als read Werke: AFAIK, ich sollte eine readsPrec Funktion anstelle von read implementieren und diese readsPrec sollte das Lesen für Streicher führen nur aus einem Zeichen. Ist das richtig?
Wenn das der Fall ist, wie sollte man dann eine Read Instanz von Tree implementieren, wenn die Analyse über erfolgt? Können wir das Parsen Wort für Wort unterbrechen oder müssen wir das tun?

Ich glaube nicht, von read als schwierig Funktion in Haskell, aber jetzt finde ich es ziemlich perplex und verwirrend, und ich bin in der Suche Hoogle für alle diese ungewohnten Dinge wie readP_to_S, verloren readS usw.

Jede Hilfe oder Referenz wird sehr geschätzt.

+1

Die Art der sein 'readsPrec' ist nur' Int -> String -> [(a, String)] '. Sie können die leere Liste zurückgeben, um einen Lesefehler zu bezeichnen, oder den Singleton '[(x," ")]', wobei "x" das Parse-Ergebnis ist, um Erfolg anzuzeigen. Wenn Sie bereits einen Parsec-Parser für Ihren Typ oder einen anderen Parser haben, führen Sie einfach diesen Parser aus und konvertieren Sie seine Ausgabe in das entsprechende Formular. – user2407038

+0

Aber das '" "' sieht falsch aus: es wird nicht den Rest der Zeichenfolge, die nicht analysiert wird, nicht zurückgeben? – awllower

Antwort

6

Ich habe mich schon seit einiger Zeit gefragt, und deine Frage hat mich dazu veranlasst, mich darum zu kümmern.

Zusammenfassung: Der einfachste Weg, um es manuell zu tun:

instance Read Tree where 
    readsPrec _ str = [(parsecRead str,"")] 

Aber deriving ist die sicherere Option; Das obige funktioniert nicht mit [Tree] und anderen Datentypen. Mein Verständnis ist, dass Show und Read sind nicht beabsichtigt, manuell implementiert werden; sie sollten abgeleitet werden und arbeiten an syntaktisch korrekte Haskell Ausdrücke.


Es sieht aus wie der Grund, dass Read nicht so einfach ist, wie

class Read a where 
    read :: String -> a 

ist, dass es System der Parser Kombinatoren ist, ähnlich, aber unterscheidet sich von Parsec, das modulare, rekursive angefertigt werden , und so weiter. Aber da wir bereits eine andere Parser-Kombinator-Bibliothek, Parsec, verwenden, denke ich, dass es am besten ist, sich so wenig wie möglich mit dem anderen System zu messen.

Die Prelude-Dokumentation besagt, dass eine minimale vollständige Implementierung von ReadreadsPrec oder readPrec ist. Letzteres wird als "Vorgeschlagener Ersatz für readsPrec unter Verwendung von Parsern neuen Typs (nur GHC)" beschrieben. Das riecht nach Ärger für mich, also lassen Sie uns mit der Implementierung readsPrec gehen.

Der Typ ist

readsPrec :: Read a => Int -> ReadS a 
type ReadS a = String -> [(a,String)] 

und die Dokumentation für ReadS liest „ein Parser für einen Typ a, als eine Funktion dargestellt, die eine String und gibt eine Liste von möglichen parst als (a,String) Paare nimmt“.Für mich ist es nicht ganz klar, was für ein „analysieren“, aber ein Blick auf den source code for read in Text.Read ist aufschlussreich:

read :: Read a => String -> a 
read s = either errorWithoutStackTrace id (readEither s) 

readEither :: Read a => String -> Either String a 
readEither s = 
    -- minPrec is defined as 0 in Text.ParserCombinators.ReadPrec 
    case [ x | (x,"") <- readPrec_to_S read' minPrec s ] of 
     [x] -> Right x 
     [] -> Left "Prelude.read: no parse" 
     _ -> Left "Prelude.read: ambiguous parse" 
    where 
    read' = -- read' :: P.ReadPrec a 
    do x <- readPrec 
     lift P.skipSpaces -- P is Text.ParserCombinators.ReadP 
     return x 

ich die Definitionen von readPrec_to_S usw. zu erweitern versucht, aber ich fühlte es nicht wert war . Ich denke, die Definition macht deutlich, dass wir [(x,"")] als erfolgreiche Analyse zurückgeben sollten.

Das Ganzzahl-Argument zu readsPrec scheint der "Vorrangkontext" zu sein. Meine Vermutung ist, dass es sicher ist, es zu ignorieren, wenn wir nur einen Baum nach dem anderen analysieren wollen, aber das Ignorieren führt später zu Problemen, wenn wir zum Beispiel Instanzen von [Tree] analysieren. Ich werde es ignorieren, weil ich nicht denke, dass es die Mühe wert ist.

Kurz gesagt, wenn wir parsecRead :: String -> Tree haben, wie in der Post definieren Sie beziehen (der Autor nannte es read')

instance Read Tree where 
    readsPrec _ str = [(parsecRead str,"")] 

Wenn wir überprüfen, wie dies in einem Programm arbeitet (mit der Show Instanz, dass die ursprünglichen Fragesteller zur Verfügung gestellt):

main = do 
    print (read "ABC(DE)F" == example) 
    print ([read "ABC(DE)F", read "ABC(DE)F"] :: [Tree]) 
    print (read "[ABC(DE)F,ABC(DE)F]" :: [Tree]) 

wir

True 
[ABC(DE)F,ABC(DE)F] 
Test.hs: Prelude.read: no parse 
erhalten

Die Komplexität und der Mangel an Dokumentation hier macht mich tatsächlich denken, dass deriving (Read) ist eigentlich die einzige sichere Option, es sei denn, Sie sind bereit, in die Details der Präzedenzstufen einzutauchen. Ich glaube, ich habe irgendwo gelesen, dass Show und Read sind eigentlich hauptsächlich abgeleitet werden soll, und dass die Saiten sein sollen syntaktisch korrekt Haskell Ausdrücke (bitte korrigieren Sie mich, wenn ich falsch liege). Für eine allgemeinere Analyse sind Bibliotheken wie Parsec wahrscheinlich die bessere Option.

Wenn Sie die Energie in den Quellcode selbst zu suchen, wird die entsprechende Module

+2

'liestPrec _ str = [(parsecRead str," ")] scheint eine schlechte Idee zu sein. Es ignoriert vollständig Parse-Fehler und passt nicht gut zu Funktionen, die erwarten, dass "readsPrec" die nicht geparsten Bits der Zeichenfolge zurückgibt. Und noch dazu ist es nicht einmal notwendig, auf diese Weise schlecht zu sein: Parsec ist perfekt in der Lage, Parse-Fehler zu melden und perfekt den nicht geparsten Teil der Eingabe-Zeichenkette zurückzugeben. –

+0

@DanielWagner Das klingt, als wäre es eine Verbesserung! Können Sie ein Beispiel geben? Ich bin mit Parsec nicht sehr vertraut und ich kann keinen offensichtlichen Weg finden, den entarteten Teil in der Dokumentation zurückzugeben. –

+0

[Hier ist ein Proof of Concept.] (Http: // lpaste.net/175029) In ghci erzeugt 'parsecToReads (char 'a' <|> char 'b') 'abc'' '[(' a ',' bc ')] ''. –