猜谜游戏。 每张卡都包含一个西装,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)无效
答案 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
毫无意义-您仍然需要使用x
和xs
来说明实际的卡号!例如。 Just $ Card x xs
(尽管仍然无法使用,因为它们仍然是字符/字符串而不是Suit / Rank)。
一种解决方案是在Read
,Rank
和Suit
上自动派生一个Card
实例。但是,read
上Card
的自动派生将需要您输入例如。 "Card A R1"
,因此让我们尝试使用Rank
和Suit
上的实例,让我们为不需要前缀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
接受三个参数: