gpt4 book ai didi

parsing - 在 Haskell 中创建一个 Read 实例

转载 作者:行者123 更新时间:2023-12-02 02:24:10 24 4
gpt4 key购买 nike

我有一个数据类型

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 之间应该是对称的,所以在阅读之后(但不是真正(我认为)理解)thisthis ,并阅读 documentation ,我想出了以下代码:

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 和/或向我展示的正确方法吗?

最佳答案

我将使用 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”。我不确定这是一个好主意,所以也许我们可以添加另一个测试长度小时== 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]

关于parsing - 在 Haskell 中创建一个 Read 实例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14006707/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com