实例声明并将字符串转换为Maybe类型

时间:2019-04-07 10:40:20

标签: haskell

猜谜游戏。 每张卡都包含一个西装,A,B,C,D,E,F,G中的一个,以及一个等级,1 | 2 | 3中的一个。

我需要一个函数来保证input(String)是否为有效类型。还要编写一个实例声明,以使类型位于Show类中。

我不确定toCard函数,如何在函数中表达“ String”并满足条件。

data Suit = A|B|C|D|E|F|G 
            deriving (Show , Eq)

data Rank = R1|R2|R3
              deriving (Show, Eq)

data Card = Card Suit Rank
             deriving (Show, Eq)

instance Show Card where show (Card a b) 
     = show a ++ show (tail show b)

toCard :: String -> Maybe Card
toCard all@(x:xs)
        | (x=A|B|C|D|E|F)&&(xs==1|2|3) = Just Card
        | otherwise  = Nothing

编辑toCard函数,输入应该是任何String,所以我使用列表表达式,但是似乎不正确,因为我在ghci中尝试了它,(x = A | B | C | D | E | F) &&(y == 1 | 2 | 3)无效

2 个答案:

答案 0 :(得分:8)

1)首先,

instance Show Card where show (Card a b) 
     = show a ++ show (tail show b)

您已自动为Show派生了一个Card实例,因此这将发生冲突(您只能有1个实例),并且进一步将无法编译。 show应该换行,并且tail应该应用于show b结果

instance Show Card where 
    show (Card a b) = show a ++ " " + tail (show b)

2)其次,

toCard :: String -> Maybe Card
toCard all@(x:xs)
        | (x=A|B|C|D|E|F)&&(xs==1|2|3) = Just Card
        | otherwise  = Nothing

语法(x=A|B|C|D|E|F)&&(xs==1|2|3)非常狂野,并且肯定不是有效的Haskell。最接近的近似值类似于x `elem` ['A','B','C','D','E','F'] && xs `elem` ["1","2","3"],但是如您所见,它很快就变成了样板。另外,Just Card毫无意义-您仍然需要使用xxs来说明实际的卡号!例如。 Just $ Card x xs(尽管仍然无法使用,因为它们仍然是字符/字符串而不是Suit / Rank)。

一种解决方案是在ReadRankSuit上自动派生一个Card实例。但是,readCard的自动派生将需要您输入例如。 "Card A R1",因此让我们尝试使用RankSuit上的实例,让我们为不需要前缀Card的{​​{1}} s编写一个解析器。

第一次尝试:

"Card"

嗯,这实际上并不能使我们处理错误的输入-问题是toCard :: String -> Maybe Card toCard (x:xs) = Just $ Card (read [x] :: Suit) (read xs :: Rank) 只会引发错误,而不是给我们read。但是请注意,我们使用Maybe而不是[x],因为x适用于read[Char]。下次尝试:

x :: Char

这可以更好地应对输入错误的情况,但是开始变得很长。这是再次缩短它的两种可能方法:

import Text.Read (readMaybe)

toCard :: String -> Maybe Card
toCard [] = Nothing
toCard (x:xs) = let mSuit = readMaybe [x] :: Suit
                    mRank = readMaybe xs :: Rank
                in case (mSuit, mRank) of
                    (Just s, Just r) -> Just $ Card s r
                    _ -> Nothing

答案 1 :(得分:4)

解析器库尽管具有更陡峭的学习曲线,但使此过程更简单。在此示例中,我们将使用Text.Parsec库中的parsec。导入它,并定义用于定义解析器的类型别名。

import Text.Parsec
type Parser = Parsec String ()  -- boilerplate

Parsec String ()表示一种分析器,它消耗字符流,并且在分析完成后可以产生类型()的值。 (在这种情况下,我们只关心解析,而不考虑解析旁边进行的任何计算。)

对于您的核心类型,我们将手动为Show定义一个Rank实例,这样您以后就无需剥离R。我们还将派生一个Read的{​​{1}}实例,以使将Suit之类的字符串转换为"A"之类的Suit值变得更加容易。

A

通过这种方式,我们可以为每种类型定义一些解析器。

data Suit = A|B|C|D|E|F|G deriving (Show, Read, Eq)

data Rank = R1|R2|R3 deriving (Eq)
-- Define Show yourself so you don't constantly have to remove the `R`
instance Show Rank where
    show R1 = "1"
    show R2 = "2"
    show R3 = "3"

data Card = Card Suit Rank deriving Eq
instance Show Card where
    show (Card s r) = show s ++ show r

-- Because oneOf will fail if you don't get one -- of the valid suit characters, read [s] is guaranteed -- to succeed. Using read eliminates the need for a -- large case statement like in rank, below. suit :: Parser Suit suit = do s <- oneOf "ABCDEF" return $ read [s] rank :: Parser Rank rank = do n <- oneOf "123" return $ case n of '1' -> R1 '2' -> R2 '3' -> R3 -- A Card parser just combines the Suit and Rank parsers card :: Parser Card card = Card <$> suit <*> rank -- parse returns an Either ParseError Card value; -- you can ignore the exact error if the parser fails -- to return Nothing instead. toCard :: String -> Maybe Card toCard s = case parse card "" s of Left _ -> Nothing Right c -> Just c 是一个预定义的解析器,它完全使用给定列表中的一项。

oneOf接受三个参数:

  1. 解析器
  2. “源名称”(仅用于错误消息;您可以使用任何喜欢的字符串)
  3. 要解析的字符串