使用整数值在Haskell中构造另一种数据类型

时间:2015-11-12 04:50:35

标签: haskell types

我正在尝试定义具有扑克牌等级的数据类型。

  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’

3 个答案:

答案 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

- 不,这不起作用,因为这会导致价值123之类的奇怪内容属于DataType类型,而很明显它们(也)类型为Int

1 :: Int
1 :: DataType  -- what you are attempting

同时,IntDataType中的其他一些值

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的任何事情,最后将这些代码用于无聊,无信息和不必要的运行时检查。

因此,虽然这样的解决方案在技术上解决了这个问题 - 没有编译错误,你可以编写你的程序 - 你经常处于边缘,需要确保没有一些较低级别的流氓变得流氓,生活在纸牌游戏的某个地方,假装是9991 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