【问题标题】:Reading a custom data type with error Prelude.read: no parse读取带有错误 Prelude.read: no parse 的自定义数据类型
【发布时间】:2020-01-31 22:53:03
【问题描述】:

我对函数式编程非常陌生,并且我很少有以下自定义数据类型来表示一副纸牌。

西装的数据类型

data Suit = Spade | Club | Diamond | Heart
  deriving (Eq, Ord, Enum, Bounded)

instance Show Suit where
  show Spade = "S"
  show Club = "C"
  show Diamond = "D"
  show Heart = "H"

instance Read Suit where
  readsPrec _ [] = []
  readsPrec _ (s: str) = case s of
    'S' -> [(Spade, str)]
    'C' -> [(Club, str)]
    'D' -> [(Diamond, str)]
    'H' -> [(Heart, str)]
    _ -> []

排名的数据类型

data Rank = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten
          | Jack | Queen | King | Ace
  deriving (Eq, Ord, Enum, Bounded)

instance Show Rank where
  show Ace = "A"
  show King = "K"
  show Queen = "Q"
  show Jack = "J"
  show rank = show $ fromEnum rank + 2

instance Read Rank where
  readsPrec _ [] = []
  readsPrec _ (s: str) = case s of
      'A' -> [(Ace, str)]
      'K' -> [(King, str)]
      'Q' -> [(Queen, str)]
      'J' -> [(Jack, str)]
      _   -> readNum
    where
      readNum
        | s >= '2' && s <= '9' = [(toEnum (ord s - ord '2') :: Rank, str)]
        | s == '1' = readZero str -- Read a following 0
        | otherwise = []
      readZero ('0': rest) = [(Ten, rest)]
      readZero _ = []

卡片的数据类型

data Card = Card Suit Rank
  deriving (Eq)

instance Show Card where
  show (Card suit rank) = show suit ++ show rank

instance Read Card where
  readsPrec _ str = do
     (s, rest) <- reads str
     (r, end) <- reads rest
     return (Card s r, end)

当我在我的堆栈 ghci 控制台中输入 read "S3H5" :: Card 时,它显示了一个异常错误 *** Exception: Prelude.read: no parse

请问如何解决这个问题?

我有一串卡片信息,例如“S3HA”代表两张卡片:

  • 铁锹三
  • 心脏王牌

我想要达到的目标:

当我读取“S3HA”的字符串时,我希望输出是一个数组

[Card Spade Three, Card Heart Ace]

请指教!我对 Haskell 函数式编程比较陌生!

并且我不想修改上述CardSuitRank 的自定义数据类型的源代码,而是利用这些数据类型创建[Card] 的数组。谁能提供将字符串解析为[Card]的方法?

【问题讨论】:

  • read "S3" :: Card 工作正常。问题是您不能说read "S3H5" :: [Card],因为Read 的列表实例假定该字符串包含一个实际的列表文字,而不仅仅是您要读取的列表的a 类型的并列字符串。
  • 那我如何阅读才能产生输出[Card Spade Three, Card Heart Ace]
  • 我认为你可以使用FlexibleInstances 扩展来为Read 定义你自己的[Card] 实例(它还必须以某种方式使用状态单子来遍历字符串;@987654341 @ 正在尝试从字符串中的 每个 字符创建卡片,而不仅仅是从字符串中读取单个字符并继续)。不过,我建议使用适当的解析库,例如 parsec 或其变体之一,而不是 Read
  • 鉴于我的任务要求不允许我修改源代码(对于CardSuitRank 的自定义数据类型,我认为我不能这样做上面,但只利用这些数据类型来创建输出[Card]的数组。我不知道如何用解析方法来实现这个输出(我对haskell和解析的了解很浅),你能提供一个例子来说明如何解析[Card] 的输出?谢谢。
  • 对于列表,我认为您需要在 Read Card 实例中重新定义 readList 方法。如果你这样做,那么预定义的instance Read a =&gt; Read [a] 将自动调用你自己的readList,一切都应该正常工作。 hackage.haskell.org/package/base-4.12.0.0/docs/…

标签: parsing haskell functional-programming typeclass


【解决方案1】:

正如@chi 在问题的cmets 中所说,实现目标的最直接方法是为Card 定义readList 方法。

对于列表,我认为您需要在 Read Card 实例中重新定义 readList 方法。如果你这样做,那么预定义的实例Read a =&gt; Read [a] 将自动调用你自己的readList,一切都应该正常工作。

一种可能的定义:

instance Read Card where
  ...

  readList str = do
      (card, rest) <- reads str
      if null rest then return ([card], "")
          else fmap (Data.Bifunctor.bimap (card:) id) (readList rest

或许:

import Text.ParserCombinators.ReadP

...

instance Read Card where
  ...

  readList = readP_to_S (many (readS_to_P reads))

 

不过,还要注意默认的ShowRead 实例有一些优势:

  • 这很正常——也就是说,人们会知道会发生什么。对于自定义实例,必须检查实例定义才能正确使用它,而默认实例可以立即轻松地使用(除了大量输入)
  • 它是自动生成的,因此不太可能出现错误。它免费为您提供了坚实的基础,而如果您想真诚地发布代码,则需要彻底检查自定义定义。

因此,将相同的代码放入单态函数中可能更合理,例如 parseCardparseCardsrenderCardrenderCards,并且两全其美。

 

实际上,我认为自定义 Read 实例是 Haskell 的一个不起眼的角落。当我前段时间研究类似问题时,我找不到有关此主题的任何指南。 (与关于元胞自动机或免费单子的大量指南进行比较。) 这可能是因为人们大多使用默认派生实例,直到他们真正需要解析大量数据,即当高级解析库(例如 parsec)开始发挥作用时。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-28
    • 2020-09-07
    • 2014-07-29
    • 1970-01-01
    • 1970-01-01
    • 2019-02-14
    • 2014-09-08
    相关资源
    最近更新 更多