20

私はデータ型を持っています

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

Showのインスタンスを次のように定義しました

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))

時間を。の形式で出力します07:09

Showさて、との間には対称性があるはずなので、これこれReadを読んだ後(しかし本当に(私は思うに)理解していない) 、そしてドキュメントを読んだ後、私は次のコードを思いついた:

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

これは機能しますが、""一部が間違っているように見えます。だから私の質問は次のようになります:

誰かが私に、Readを実装して解析"07:09"newTime 7 9たり表示したりする正しい方法を説明できますか?

4

2 に答える 2

22

私はisDigitあなたの時間の定義を使用し、維持します。

import Data.Char (isDigit)

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

あなたは使用しましたが、定義していなかったnewTimeので、コードをコンパイルするために自分で作成しました。

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

まず、showインスタンスは少し間違っていshow $ Time 10 10ます。"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))

見てみましょう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

""これはパーサーです。これは、単なるではなく、一致しない残りの文字列を返す必要があるため、""間違いです。

*Main> read "03:22" :: Time
03:22
*Main> read "[23:34,23:12,03:22]" :: [Time]
*** Exception: Prelude.read: no parse

,23:12,03:22]最初の読み取りでを破棄したため、解析できません。

少しリファクタリングして、入力を処理していきましょう。

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

たとえば与える

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

ただし、「3:45」を許可し、「03:45」として解釈します。それが良い考えかどうかはわかりませんので、別のテストを追加することもできlength hours == 2ます。


私たちがこのようにそれをしているなら、私はこのすべての分割とスパンのものをやめます、それで多分私は好むでしょう:

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

これは実際、私にはすっきりとシンプルに思えます。

今回は許可されていません"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]
于 2012-12-22T22:03:00.130 に答える
4

への入力readsPrecが、の有効な表現の後に他の文字を含む文字列である場合Time、それらの他の文字はタプルの2番目の要素として返される必要があります。

したがって、文字列12:34 blaの場合、結果はになります[(newTime 12 34, " bla")]。実装すると、その入力に対してエラーが発生します。これは、引数として'sをread "[12:34]" :: [Time]呼び出すため、のようなものが失敗することを意味します(。を消費し、残りの文字列を使用して呼び出し、によって返される残りの文字列が、またはコンマの後にさらに要素が続くことを確認するため)。TimereadsPrec"12:34]"readList[readsPrecreadsPrec]

修正するには、のような名前にreadsPrec変更してから、実際の分の部分(たとえば)とその分の後に続くものに分割する必要があります。次に、分部分の後に来るものは、タプルの2番目の要素として返される必要があります。minutePartafterColontakeWhile isDigit

于 2012-12-22T21:41:05.803 に答える