簡體   English   中英

在Haskell中創建一個Read實例

[英]Making a Read instance in Haskell

我有一個數據類型

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的格式打印出時間。

現在, ShowRead之間應該是對稱的,所以在讀完(但不是真的(我認為)理解) 這個這個 ,並閱讀文檔后 ,我想出了以下代碼:

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 "07:09"newTime 7 9和/或告訴我嗎?

我將使用isDigit並保留您對Time的定義。

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 $ 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]

如果readsPrec的輸入是在Time的有效表示之后包含一些其他字符的字符串,那么應該返回那些其他字符作為元組的第二個元素。

所以對於字符串12:34 bla ,結果應該是[(newTime 12 34, " bla")] 您的實現會導致該輸入的錯誤。 這意味着read "[12:34]" :: [Time]會失敗,因為它會調用TimereadsPrec"12:34]"作為參數(因為readList將使用[ ,然后調用readsPrec與剩下的字符串,然后檢查readsPrec返回的剩余字符串是否為]或逗號后跟更多元素)。

要修復你的readsPrec你應該將minutePart重命名為afterColon ,然后將其拆分為實際的分鍾部分(例如takeWhile isDigit )以及分鍾部分之后的任何內容。 那么在分鍾部分之后出現的東西應該作為元組的第二個元素返回。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM