读取错误为Prelude.read的自定义数据类型:无解析

时间:2019-10-02 16:04:21

标签: parsing haskell functional-programming typeclass

我对函数式编程非常陌生,我几乎没有以下自定义数据类型来代表一副纸牌。

西服的数据类型

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的自定义数据类型的源代码,而是利用这些数据类型来创建{{1 }}。有人可以提供将字符串解析为[Card]的方法吗?

1 个答案:

答案 0 :(得分:0)

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

  

对于列表,我认为您需要在readList实例中重新定义Read Card方法。如果这样做,则预定义实例Read a => 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的一个晦涩角落。前一段时间我在研究类似问题时,找不到关于此主题的任何指南。 (与诸如细胞自动机或免费monad上的大量指南进行比较。)这可能是因为人们大多使用默认派生实例,直到他们真正需要解析大量数据为止,这就是当高级解析库(例如parsec)发挥作用时。