我正在尝试定义具有扑克牌等级的数据类型。
data Suit = Spades | Clubs | Hearts | Diamonds deriving Show
data Rank = 2|3|4|5|6|7|8|9|10 | Jack | Queen | King | Ace deriving Show
data Card = Card Rank Suit
我无法将数字[2..10]
合并为排名,我确实尝试用LowerRanks Int
替换数字,它会运行代码很好,但它不允许我调出任何数字数字。
data Suit = Spades | Clubs | Hearts | Diamonds deriving Show
data Rank = LowerRank Int | Jack | Queen | King | Ace deriving Show
data Card = Card Rank Suit
instance Show Card where
show (Card x y) = show x ++ " of " ++ show y
我可以写:
Card Jack Spades
我将获得Jack of Spades
。但是,当我尝试编写Card 2 Spades
时,出现错误:
No instance for (Num Rank) arising from the literal ‘2’
In the first argument of ‘Card’, namely ‘2’
答案 0 :(得分:7)
数据构造函数必须以大写字母或冒号开头,数字不是有效的构造函数。你可以写出等级,例如:
data Rank = Two | Three | Four | ...
正如您所指出的,您也可以拥有一个完整的字段:
data Rank = RNum Int | Jack | Queen | King | Ace
旁注:我不明白这个问题"它不允许我拨打任何号码"。将来请发布您尝试的实际代码和错误消息。请随意澄清,我将编辑此答案。
使用Rank
的第二个版本,我们可以构建每张卡片,例如:
twoH = Card (RNum 2) Hearts
threeD = Card (RNum 3) Dimonds
值得为等级制作一个自定义的有界实例,并为Card,Suit提供有限的。导出Ord
也会很好,但是你想要在大多数纸牌游戏中按照正确的顺序放置套装。
答案 1 :(得分:4)
基本上所有这些归结为一些简单的原则。我稍后还将介绍一些基本的设计模式。
数据类型是通过构造函数获得的独立的,独立的值集,具体而言,它是具有“生成”值 ex nihilo 的定义主体的魔术函数,排序的:
data DataType = Constr1 | Constr2
有时您希望将来自其他类型的值嵌入到您的类型的值中;你通过参数化构造函数来做到这一点:
data DataType = Constr1 | Constr2 | Constr3 Int
现在,Int
类型的值可以嵌入DataType
的值中,但只能作为使用Constr3
构建的值的一部分。
现在,应该变得明显的是,您不能简单地将Int
类型的值直接放入DataType
定义的集合中:
data DataType = Constr1 | Constr2 | Constr3 Int | 1 | 2 | 3
- 不,这不起作用,因为这会导致价值1
,2
和3
之类的奇怪内容属于DataType
类型,而很明显它们(也)类型为Int
:
1 :: Int
1 :: DataType -- what you are attempting
同时,Int
中DataType
中的其他一些值
4 :: DataType -- you want to AVOID this.
所以你可以看到,这很快就让我们变得荒谬。而这正是你在不知不觉中指的是当你说“它不允许我喊出任何数字”时。
所以让我们回到来“包装”构造函数,如Constr3
所示:
data Rank = Lower Int | Jack | Queen | King | Ace
这使我们能做的是:
Jack :: Rank -- of course
Ace :: Rank -- of course
Lower 3 :: Rank -- obviously
但也
Lower 999 :: Rank -- what?
几乎完全违背了完全使用类型安全Rank
的目的,因此我们不妨在整个14中使用整数2来表示任何等级,放弃静态类型安全性 - 但是我们不希望这样,因为我们必须关注避免遇到低于2或高于14的任何事情,最后将这些代码用于无聊,无信息和不必要的运行时检查。
因此,虽然这样的解决方案在技术上解决了这个问题 - 没有编译错误,你可以编写你的程序 - 你经常处于边缘,需要确保没有一些较低级别的流氓变得流氓,生活在纸牌游戏的某个地方,假装是999
或1 000 000
等等。
所以这是我推荐的解决方案:
data Rank = R2 | R3 | R4 | R5 | R6 | R7 | R8 | R9 | R10
| Jack | Queen | King | Ace
deriving Show
这样,一旦Rank
值被构造,你永远不会检查Rank
值的有效性。您始终可以安全地对排名进行模式匹配,而不会在编译时获得虚假模式耗尽警告,或者在运行时出现模式匹配失败。
总结,这种设计的“模式”通常被称为构造的正确性,设计的正确性,类型引导式编程以及短语“Make”非法国家无法代表“等。这种设计理念也用于F#,OCaml,Scala和其他静态类型编程语言(特别是功能性编程语言)。
此外:现在您拥有一个不错的平坦且安全的Rank
数据类型,您可能需要考虑将data Rank = R2 | R3 | R4 | R5 | R6 | R7 | R8 | R9 | R10
| Jack | Queen | King | Ace
deriving (Eq, Ord, Show)
作为Eq
的实例并且Ord
,获得免费比较:
Prelude> R2 == R3
False
Prelude> Ace > Queen
True
Prelude> R9 < Jack
True
Prelude> R9 <= R9
True
得到以下特性:
+---src
+---main
| \---python
| \---hello
| helloworld.py
| __init__.py
|
\---unittest
\---python
\---hello
helloworld_tests.py
__init__.py
或者如果您希望能够转换为整数和从整数转换,请查看Enum
。
答案 2 :(得分:0)
data Rank = Numeric Integer | Jack | Queen | King | Ace
deriving (Eq, Show)
还提供了如何随机创建它们
instance Arbitrary Rank where
arbitrary = frequency [ (1, return Jack)
, (1, return Queen)
, (1, return King)
, (1, return Ace)
, (9, do n <- choose (2, 10)
return (Numeric n))
]
从Chalmers的一个实验室借来,你在那里创建了一个黑杰克游戏http://www.cse.chalmers.se/edu/course/TDA555/Code/Lab2/Cards.hs