Mag*_*nap 19 parsing haskell typeclass symmetry
我有一个数据类型
data Time = Time {hour :: Int,
minute :: Int
}
Run Code Online (Sandbox Code Playgroud)
我为其定义了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))
Run Code Online (Sandbox Code Playgroud)
以格式打印出时间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
Run Code Online (Sandbox Code Playgroud)
这有效,但该""
部分使它看起来是错误的.所以我的问题最终是:
任何人都可以向我解释来实现读来解析正确的方式"07:09"
进入newTime 7 9
和/或告诉我?
And*_*ewC 20
我会使用isDigit
并保留你对时间的定义.
import Data.Char (isDigit)
data Time = Time {hour :: Int,
minute :: Int
}
Run Code Online (Sandbox Code Playgroud)
你使用但没有定义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
Run Code Online (Sandbox Code Playgroud)
首先,你的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))
Run Code Online (Sandbox Code Playgroud)
我们来看看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
Run Code Online (Sandbox Code Playgroud)
这是一个解析器 - 它应该返回无法匹配的剩余字符串而不仅仅是""
,所以你""
说错了:
*Main> read "03:22" :: Time
03:22
*Main> read "[23:34,23:12,03:22]" :: [Time]
*** Exception: Prelude.read: no parse
Run Code Online (Sandbox Code Playgroud)
它无法解析它,因为你,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
Run Code Online (Sandbox Code Playgroud)
举个例子
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
Run Code Online (Sandbox Code Playgroud)
但是,它确实允许"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
Run Code Online (Sandbox Code Playgroud)
这对我来说实际上看起来更干净,更简单.
这次它不允许"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]
Run Code Online (Sandbox Code Playgroud)
如果输入readsPrec
是一个字符串,在 a 的有效表示之后包含一些其他字符Time
,则这些其他字符应作为元组的第二个元素返回。
所以对于字符串12:34 bla
,结果应该是[(newTime 12 34, " bla")]
. 您的实现将导致该输入出现错误。这意味着类似的事情read "[12:34]" :: [Time]
会失败,因为它会调用Time
's readsPrec
with"12:34]"
作为参数(因为readList
会消耗[
, 然后使用readsPrec
剩余的字符串进行调用,然后检查返回的剩余字符串是否是readsPrec
逗号]
或后跟更多元素)。
要修复您的问题,readsPrec
您应该重命名minutePart
为类似的名称afterColon
,然后将其拆分为实际的分钟部分(例如takeWhile isDigit
)以及分钟部分之后的任何内容。然后,分钟部分之后的内容应该作为元组的第二个元素返回。
归档时间: |
|
查看次数: |
7257 次 |
最近记录: |