Haskell 类型 类 的部分实例化

Partial instantiation of Haskell type classes

作为某种形式的实验,我试图为 Haskell 中的纸牌游戏定义一个非常通用的逻辑。为了保证最大的抽象级别,我定义了以下类型 class:

class (Monad m) => CardGame m where
    data Deck   m :: *
    data Card   m :: *
    data Player m :: *

    game :: m () -- a description of the game's rules 

    draw :: Deck m -> m (Card m)             -- top card of deck is drawn
    pick :: Deck m -> Player m -> m (Card m) -- player selects card from a deck
    -- so on and so forth

这个 class 对我来说非常令人满意,因为它保留了将使用什么纸牌(例如塔罗牌、法国花色、德国花色等)、玩家是谁等以及什么游戏应该 运行 是一种潜在的 monad。

现在假设我想定义 War-like 纸牌游戏。这些游戏是在两个玩家之间进行的,每个玩家获得一副适合法国的纸牌游戏,但游戏本身有很多变体,所以我无法提前指定确切的规则是什么(即实例化game) 如何部分特化上面的类型 class?编译器对以下内容感到不安:

data Suit   = Heart | Diamond | Spade | Club
data Value  = Number Int | Jack | Queen | King 
data FrenchSuited = Card Suit Value


class (Game m) => War m where
    -- syntax error in the following line:
    data Card m = FrenchSuited -- any War-like game must be played on French-suited card
    deck1   :: Deck m
    deck2   :: Deck m
    player1 :: Player m
    player2 :: Player m

你看到我可以做到这一点的方法了吗?

PS: War-like 游戏真的可以用任何类型的卡玩,但这只是一个例子。我更感兴趣的是如何实现我所追求的 classes 类型的偏特化类型。

您不能对数据系列执行此操作:对于不同的 mm'Card m 必须是 Card m' 的不同类型。这是因为数据族必须是单射的,就像常规类型构造函数一样。

充其量,Card mCard m' 可以同构于 FrenchSuited

相反,类型族没有这种内射性限制,但正因为如此,它们有时会使类型检查变得更加微妙,需要用户以某种方式解决一些歧义。也就是说,您可以按如下方式要求您的类型:

{-# LANGUAGE TypeFamilies, AllowAmbiguousTypes #-}

class (Monad m) => CardGame m where
    type Deck   m :: *
    type Card   m :: *
    type Player m :: *

    game :: m () -- a description of the game's rules 

    draw :: Deck m -> m (Card m)             -- top card of deck is drawn
    pick :: Deck m -> Player m -> m (Card m) -- player selects card from a deck
    -- so on and so forth


data Suit   = Heart | Diamond | Spade | Club
data Value  = Number Int | Jack | Queen | King 
data FrenchSuited = Card Suit Value


class (CardGame m, Card m ~ FrenchSuited) => War m where
    deck1   :: Deck m
    deck2   :: Deck m
    player1 :: Player m
    player2 :: Player m

Card m ~ FrenchSuited 约束强制子类中的类型相等。

最初的问题提出了 data type classes 问题。但是代码已经使用 type families, data Deck m :: * inside type class requires/is a type family.

为了简单起见,您可以使用类型 classes 的 Player、Card、Deck 进行初始原型设计,看看事情会怎样,这样可以保持代码简单。随着结构出现以感受到压力点,结构需要更巧妙的抽象,您可以根据自己的喜好转移到类型系列。但是,这种类型族的使用看起来也很好。

另外不要忘记类型 classes/families 可以“部分实例化”(正如问题的声音):

{-# MINIMAL funA | funB #-}

也就是说,您可以将 more/all 包含到 class 中,但至少需要这些定义。