Ich verwende isDigit
und behalten Sie Ihre Definition von Zeit.
import Data.Char (isDigit)
data Time = Time {hour :: Int,
minute :: Int
}
Sie verwendet aber nicht definiert newTime
, so schrieb ich mich ein so kompiliert meinen Code!
newTime :: Int -> Int -> Time
newTime h m | between 0 23 h && between 0 59 m = Time h m
| otherwise = error "newTime: hours must be in range 0-23 and minutes 0-59"
where between low high val = low <= val && val <= high
Erstens ist Ihre Show Instanz ein wenig falsch, weil show $ Time 10 10
gibt "010:010"
instance Show Time where
show (Time hour minute) = (if hour > 9 -- oops
then (show hour)
else ("0" ++ show hour))
++ ":" ++
(if minute > 9 -- oops
then (show minute)
else ("0" ++ show minute))
Lassen Sie uns einen Blick auf readsPrec
haben:
*Main> :i readsPrec
class Read a where
readsPrec :: Int -> ReadS a
...
-- Defined in GHC.Read
*Main> :i ReadS
type ReadS a = String -> [(a, String)]
-- Defined in Text.ParserCombinators.ReadP
dass ein Parser ist - es sollte Gibt die nicht übereinstimmende verbleibende Zeichenfolge anstelle von nurzurück, so, du hast Recht, dass die ""
ist falsch:
*Main> read "03:22" :: Time
03:22
*Main> read "[23:34,23:12,03:22]" :: [Time]
*** Exception: Prelude.read: no parse
Es kann es nicht analysieren, weil Sie die ,23:12,03:22]
in der ersten Lese wegwarf.
Lassen Sie uns Refactoring, die ein bisschen um die Eingabe zu essen, wie wir entlang gehen:
instance Read Time where
readsPrec _ input =
let (hours,rest1) = span isDigit input
hour = read hours :: Int
(c:rest2) = rest1
(mins,rest3) = splitAt 2 rest2
minute = read mins :: Int
in
if c==':' && all isDigit mins && length mins == 2 then -- it looks valid
[(newTime hour minute,rest3)]
else [] -- don't give any parse if it was invalid
Gibt zum Beispiel
Main> read "[23:34,23:12,03:22]" :: [Time]
[23:34,23:12,03:22]
*Main> read "34:76" :: Time
*** Exception: Prelude.read: no parse
Sie räumt jedoch ein: „03.45“ erlauben und interpretiert sie als "03:45". Ich bin mir nicht sicher, ob das eine gute Idee ist, vielleicht könnten wir einen weiteren Test hinzufügen length hours == 2
.
Ich werde Sie alle diese Spaltung und Spanne Sachen, wenn wir es auf diese Weise tun, so würde ich vielleicht lieber:
instance Read Time where
readsPrec _ (h1:h2:':':m1:m2:therest) =
let hour = read [h1,h2] :: Int -- lazily doesn't get evaluated unless valid
minute = read [m1,m2] :: Int
in
if all isDigit [h1,h2,m1,m2] then -- it looks valid
[(newTime hour minute,therest)]
else [] -- don't give any parse if it was invalid
readsPrec _ _ = [] -- don't give any parse if it was invalid
die tatsächlich sauberer und einfacher zu mir scheint.
Dieses Mal ist es nicht erlaubt "3:45"
:
*Main> read "3:40" :: Time
*** Exception: Prelude.read: no parse
*Main> read "03:40" :: Time
03:40
*Main> read "[03:40,02:10]" :: [Time]
[03:40,02:10]
Vielen Dank für den Hinweis auf den Fehler in der Show. Dieser neue Code macht mehr Sinn und wäre korrekter gewesen, wenn nicht für die Tatsache (die ich vergessen habe zu erwähnen), dass der "intelligente Konstruktor" "newTime" bereits die Gültigkeitsprüfung durchführt. – Magnap
@Magnap Ich habe die unnötigen Checks bearbeitet und newTime benutzt. – AndrewC
@Magnap Ich muss immer noch überprüfen, dass ich Ziffern habe, bevor ich "Read" auf den '' Int''s anrufe. – AndrewC