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 Read
readsPrec
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
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
Aber das '" "' sieht falsch aus: es wird nicht den Rest der Zeichenfolge, die nicht analysiert wird, nicht zurückgeben? – awllower