Userò isDigit
e manterrò la definizione di Ora.
import Data.Char (isDigit)
data Time = Time {hour :: Int,
minute :: Int
}
Si è utilizzato, ma non ha definito newTime
, così ho scritto uno io così il mio codice compila!
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
In primo luogo, l'istanza spettacolo è un po 'sbagliato, perché show $ Time 10 10
dà "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))
Diamo uno sguardo a readsPrec
:
*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
Questo è un parser - dovrebbe restituire la stringa rimanente senza corrispondenza anziché solo ""
, in modo che tu abbia ragione che il ""
è sbagliato:
*Main> read "03:22" :: Time
03:22
*Main> read "[23:34,23:12,03:22]" :: [Time]
*** Exception: Prelude.read: no parse
Non può analizzare perché hai gettato via il ,23:12,03:22]
nella prima lettura.
refactoring di lasciare che un po 'di mangiare l'ingresso come andiamo avanti:
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
Dà ad esempio
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
Esso, tuttavia, permettere "03:45" e lo interpreta come "3:45". Non sono sicuro che sia una buona idea, quindi forse potremmo aggiungere un altro test length hours == 2
.
sto andando fuori tutta questa roba scissione e la durata se stiamo facendo in questo modo, così forse preferirei:
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
che in realtà sembra più pulito e più semplice per me.
Questa volta non permette "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]
Grazie per aver segnalato l'errore "off-one" nello show. Questo nuovo codice ha più senso, e sarebbe stato più corretto, se non fosse per il fatto (che ho dimenticato di menzionare), che il "costruttore intelligente" 'newTime' già esegue il controllo di validità. – Magnap
@Magnap Ho modificato i controlli non necessari e ho usato newTime. – AndrewC
@Magnap Ho ancora bisogno di controllare che abbia digits prima di chiamare 'read' su' Int's. – AndrewC