Haskell关于enums的方式

m09*_*m09 3 haskell types enumeration

所以.我想表示以下形式的类型:

(Card, Suit)
Run Code Online (Sandbox Code Playgroud)

代表卡片游戏中的卡片,其中Card实例将在集合中:

{2, 3, 4, 5, 6, 7, 8, 9, J, Q, K, 1}
Run Code Online (Sandbox Code Playgroud)

并且Suit将在集合中具有实例:

{S, D, H, C}
Run Code Online (Sandbox Code Playgroud)

如果那不是数字,我会处理两个数据声明:

data Suit = S | D | H | C deri...
Run Code Online (Sandbox Code Playgroud)

但显然在这些null arity类型中添加数字会失败.

所以我的问题是,如何模拟你在C中找到的枚举类型?

我想我是错误的类型系统的基本点,我将不胜感激!

编辑:我将添加一些上下文:我想表示此Euler问题中包含的数据,您可以检查,数据以1S的形式表示为spade,2D为2的钻石等. ..

我真正喜欢的是能够直接对字符串执行读操作以获得相应的对象.

Chr*_*lor 19

实际上,当我开发一个扑克机器人时,我碰巧有一个实现方便.它并不是特别复杂,但确实有效.

一,相关类型.等级和套装是枚举,而卡是明显的复合类型(带有自定义Show实例)

import Text.ParserCombinators.Parsec

data Suit = Clubs | Diamonds | Hearts | Spades deriving (Eq,Ord,Enum,Show)

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

data Card = Card { rank :: Rank
                 , suit :: Suit } deriving (Eq,Ord,Bounded)

instance Show Card where
    show (Card rank suit) = show rank ++ " of " ++ show suit
Run Code Online (Sandbox Code Playgroud)

然后我们有解析代码,它使用Parsec.您可以将其发展为更复杂,返回更好的错误消息等.

请注意,正如Matvey在评论中所说,将字符串解析为程序中的表示的问题是(或者应该)与枚举的表示方式正交.在这里,我被骗了,破了正交性:如果你想重新排列的行列(例如,有Ace以下等级Two),那么你会打破解析代码,因为解析器取决于内部表示Two0,Three1等.

更好的方法是明确地拼出所有排名parseRank(这是我在原始代码中所做的).我这样写是为了(a)节省一些空间,(b)说明原则上如何将一个数字解析成一个等级,以及(c)给你一个明确拼写出来的不良练习的例子,这样你就可以避免它在将来.

parseSuit :: Parser Suit
parseSuit = do s <- oneOf "SDCH"
               return $ case s of
                'S' -> Spades
                'D' -> Diamonds
                'H' -> Hearts
                'C' -> Clubs

parseRank :: Parser Rank
parseRank = do r <- oneOf "23456789TJQKA"
               return $ case r of
                'T' -> Ten
                'J' -> Jack
                'Q' -> Queen
                'K' -> King
                'A' -> Ace
                 n  -> toEnum (read [n] - 2)

parseCard :: Parser Card
parseCard = do r <- parseRank
               s <- parseSuit
               return $ Card { rank = r, suit = s }

readCard :: String -> Either ParseError Card
readCard str = parse parseCard "" str
Run Code Online (Sandbox Code Playgroud)

在这里,它正在行动:

*Cards> readCard "2C"
Right Two of Clubs
*Cards> readCard "JH"
Right Jack of Hearts
*Cards> readCard "AS"
Right Ace of Spades
Run Code Online (Sandbox Code Playgroud)

编辑:

@ yatima2975在评论中提到你可能会玩得很开心OverloadedStrings.我没有能够做到这么多有用的东西,但它看起来很有希望.首先,您需要通过置于{-# LANGUAGE OverloadedStrings #-}文件顶部来启用语言选项,并包含import GHC.Exts ( IsString(..) )用于导入相关类型类的行.然后你可以创建Card一个字符串文字:

instance IsString Card where
    fromString str = case readCard str of Right c -> c
Run Code Online (Sandbox Code Playgroud)

这允许您对卡的字符串表示进行模式匹配,而不是必须明确地写出类型:

isAce :: Card -> Bool
isAce "AH" = True
isAce "AC" = True
isAce "AD" = True
isAce "AS" = True
isAce _    = False
Run Code Online (Sandbox Code Playgroud)

您还可以使用字符串文字作为函数的输入:

printAces = do
    let cards = ["2H", "JH", "AH"]
    mapM_ (\x -> putStrLn $ show x ++ ": " ++ show (isAce x)) cards
Run Code Online (Sandbox Code Playgroud)

在这里,它正在行动:

*Cards> printAces
Two of Hearts: False
Jack of Hearts: False
Ace of Hearts: True
Run Code Online (Sandbox Code Playgroud)


dav*_*420 9

data Card = Two | Three | Four | Five | Six
          | Seven | Eight | Nine | Ten
          | Jack | Queen | King | Ace
    deriving Enum
Run Code Online (Sandbox Code Playgroud)

实现Enum类型类意味着你可以使用fromEnumtoEnum转换CardInt.

但是,如果对你很重要的fromEnum Two2,你将不得不实施Enum实例Card手.(autoderived实例从0C 开始,就像C一样,但如果不自己完成,就没有办法覆盖它.)

你可能不需要Enum---如果你想要的只是使用<==你的Cards 一样的操作符,那么你需要使用deriving Ord.


编辑:

不能使用read把一个String形式"2S""QH"(Card, Suit)因为read将期望字符串的样子"(a,b)"(例如,"(2,S)"在您最初要求的形式,或"(Two,S)"在形式上面我的建议).

您必须编写一个函数来自己解析字符串.您可以使用解析器(例如Parsec或Attoparsec),但在这种情况下,它应该足够简单以便手动编写.

例如

{-# LANGUAGE TupleSections #-}

parseSuit :: String -> Maybe Suit
parseSuit "S" = Just S
...
parseSuit _   = Nothing

parseCard :: String -> Maybe (Card, Suit)
parseCard ('2' : s) = fmap (Two,) (parseSuit s)
...
parseCard _         = Nothing
Run Code Online (Sandbox Code Playgroud)