2012-12-22 11 views
13

ich einen DatentypEin Read Beispiel in Haskell

data Time = Time {hour :: Int, 
        minute :: Int 
       } 

für die ich die Instanz von Show definiert als

instance Show Time where 
    show (Time hour minute) = (if hour > 10 
          then (show hour) 
          else ("0" ++ show hour)) 
          ++ ":" ++ 
          (if minute > 10 
          then (show minute) 
          else ("0" ++ show minute)) 

ist, das 07:09 in einem Format Mal druckt.

Nun sollte es Symmetrie zwischen Show und Read, so nach dem Lesen (aber nicht wirklich (ich glaube) Verständnis) this und this und die documentation lesen, ich habe mit dem folgenden Code kommen:

instance Read Time where 
    readsPrec _ input = 
    let hourPart = takeWhile (/= ':') 
     minutePart = tail . dropWhile (/= ':') 
    in (\str -> [(newTime 
        (read (hourPart str) :: Int) 
        (read (minutePart str) :: Int), "")]) input 

Dies funktioniert, aber der "" Teil scheint es falsch zu machen. Also meine Frage endet sein:

Kann mir jemand erklären, die richtige Art und Weise zu implementieren Lesen, um "07:09" in newTime 7 9 zu analysieren und/oder zeigen Sie mir?

Antwort

16

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] 
+1

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

+0

@Magnap Ich habe die unnötigen Checks bearbeitet und newTime benutzt. – AndrewC

+0

@Magnap Ich muss immer noch überprüfen, dass ich Ziffern habe, bevor ich "Read" auf den '' Int''s anrufe. – AndrewC

4

Wenn die Eingabe in readsPrec eine Zeichenfolge ist, die einige andere Zeichen nach einer gültigen Darstellung eines Time enthält, sollten diese anderen Zeichen als zweites Element des Tupels zurückgegeben werden. Für die Zeichenfolge 12:34 bla sollte das Ergebnis [(newTime 12 34, " bla")] sein. Ihre Implementierung würde einen Fehler für diese Eingabe verursachen. Das bedeutet, dass so etwas wie read "[12:34]" :: [Time] würde scheitern, weil es Time ‚s readsPrec mit "12:34]" als Argument nennen würde (weil readList die [ verbrauchen würde, dann rufen Sie readsPrec mit dem restlichen Zeichenfolge, und dann prüfen, ob die verbleibende Zeichenfolge zurückgegeben von readsPrec entweder ] oder ein Komma gefolgt von weiteren Elementen).

beheben Ihre readsPrec Sie minutePart so etwas wie afterColon umbenennen sollten und dann aufgeteilt, dass in den eigentlichen winzigen Teil (mit takeWhile isDigit zum Beispiel) und was auch immer nach dem winzigen Teil kommt. Dann sollten die Sachen, die nach dem Minuten-Teil kamen, als zweites Element des Tupels zurückgegeben werden.

+0

Vielen Dank. Nach dem Ändern der Let to (eingeklammert für Lesbarkeit) '{HourPart = takeWhile (/ = ':') Eingabe; afterColon = Schweif. dropWhile (/ = ':') $ input; minutePart = takeWhile isDigit afterColon; rest = dropWhile isDigit afterColon;} 'es funktioniert wunderbar. – Magnap