在Haskell中创建一个Read实例 [英] Making a Read instance in Haskell

查看:126
本文介绍了在Haskell中创建一个Read实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个数据类型

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

我已将Show的实例定义为

  instance显示时间其中
显示(时间小时分钟)=(如果小时> 10
则(显示小时数)
else(0++ show hour))
++:++
(if minute> 10
then(show minute)
else 0++显示分钟))

打印时间格式为 07:09



现在, Show 阅读,所以在阅读后(但不是真的(我认为)理解)这个 this ,然后阅读

 实例读取时间在哪里
readsPrec_input =
let hourPart = takeWhile(/ =':')
minutePart = tail。在(\str - > [(newTime
(read(hourPart str):: Int)
(read(minutePart str):: Int)中添加dropWhile(/ =':')
),)])input

这可以工作,但 部分使它看起来不对。所以我的问题最终是:



任何人都可以向我解释实现Read解析07:09转换为 newTime 7 9 和/或展示我?

解决方案

我将使用 isDigit 并保留Time的定义。

  import Data.Char(isDigit)

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

您使用过但未定义 newTime ,所以我自己编写了一个代码,以便编译代码!

  newTime :: Int  - > Int  - >时间
newTime h m |在0 23 h&&在0 59 m =时间h m
|之间否则=错误新时间:小时必须在范围0-23和分钟0-59
之间,其中低高值=低<= val&& val <= high






首先,您的show instance有点不对,因为 show $ Time 10 10 给出010:010

  instance显示时间其中
显示(时间小时分钟)=(如果小时> 9 - oops
then(show hour)
else(0++ show hour))
++:++
(如果分钟> 9 - oops
then(show minute)$ b $我们来看看 readsPrec

  * Main> :我读取了Prec 
类读取一个
readsPrec :: Int - >读取
...
- 在GHC.Read中定义
* Main> :我ReadS
类型ReadS a = String - > [(a,String)]
- 在Text.ParserCombinators.ReadP中定义

这就是一个解析器 - 它应该返回不匹配的剩余字符串,而不是,所以你正确的是是错误的:

  * Main>阅读03:22:: Time 
03:22
* Main>阅读[23:34,23:12,03:22]:: [时间]
***例外:Prelude.read:不解析

它无法解析它,因为您丢弃了,23:12,03:22] 首先阅读。



让我们重复一下,以便随时了解输入:

 实例读取时间其中
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 ==':'&&全部是数字&&长度分钟== 2然后 - 它看起来有效
[(newTime hour minute,rest3)]
else [] - 如果它是无效的,不给任何解析

举例

  Main> ;阅读[23:34,23:12,03:22]:: [时间] 
[23:34,23:12,03:22]
* Main>读34:76::时间
***例外:Prelude.read:不分析



然而,它确实允许3:45并将其解释为03:45。我不确定这是个好主意,所以也许我们可以添加另一个测试长度小时== 2






如果我们这样做,我会把所有这些分割和跨度的东西都去掉, d比较喜欢:

$ $ p $ $ code $实例读取时间
readsPrec _(h1:h2:':':m1:m2:那么)=
let hour = read [h1,h2] :: Int - 懒惰不会被计算,除非有效
minute =读取[m1,m2] :: Int
in
如果全部是数字[h1,h2,m1,m2],那么 - 它看起来有效
[(newTime hour minute,therest)]
else [] - 不给出任何解析if它是无效的
readsPrec _ _ = [] - 如果它是无效的,不给任何解析

这实际上对我来说看起来更清洁和简单。



这一次它不允许3:45

  * Main>阅读3:40:: Time 
***例外:Prelude.read:不分析
* Main>阅读03:40:: Time
03:40
* Main>阅读[03:40,02:10]:: [时间]
[03:40,02:10]


I have a data type

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

for which i have defined the instance of Show as being

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

which prints out times in a format of 07:09.

Now, there should be symmetry between Show and Read, so after reading (but not truly (i think) understanding) this and this, and reading the documentation, i have come up with the following code:

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

This works, but the "" part makes it seem wrong. So my question ends up being:

Can anyone explain to me the correct way to implement Read to parse "07:09" into newTime 7 9 and/or show me?

解决方案

I'll use isDigit and keep your definition of Time.

import Data.Char (isDigit)

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

You used but didn't define newTime, so I wrote one myself so my code compiles!

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


Firstly, your show instance is a little wrong because show $ Time 10 10 gives "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))

Let's have a look at 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

That's a parser - it should return the unmatched remaining string instead of just "", so you're right that the "" is wrong:

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

It can't parse it because you threw away the ,23:12,03:22] in the first read.

Let's refactor that a bit to eat the input as we go along:

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

Gives for example

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

It does, however, allow "3:45" and interprets it as "03:45". I'm not sure that's a good idea, so perhaps we could add another test length hours == 2.


I'm going off all this split and span stuff if we're doing it this way, so maybe I'd prefer:

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

Which actually seems cleaner and simpler to me.

This time it doesn't allow "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]

这篇关于在Haskell中创建一个Read实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆