Haskell类型类的部分实例化

Haskell类型类的部分实例化,haskell,typeclass,type-families,Haskell,Typeclass,Type Families,作为某种形式的实验,我试图在Haskell中为纸牌游戏定义一个非常通用的逻辑。为了保证最大程度的抽象,我定义了以下类型类: class(Monad m)=>CardGame m其中 数据甲板m::* 数据卡m::* 数据播放器m::* 游戏::m()--游戏规则的描述 抽牌::牌组m->m(牌组m)--抽牌组顶部的牌 拾取::牌组m->玩家m->m(牌m)--玩家从牌组中选择牌 --诸如此类 这门课让我非常满意,因为它让我知道将使用什么样的牌(例如塔罗牌、法式套牌、德式套牌等)、玩家是谁等等,

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

class(Monad m)=>CardGame m其中
数据甲板m::*
数据卡m::*
数据播放器m::*
游戏::m()--游戏规则的描述
抽牌::牌组m->m(牌组m)--抽牌组顶部的牌
拾取::牌组m->玩家m->m(牌m)--玩家从牌组中选择牌
--诸如此类
这门课让我非常满意,因为它让我知道将使用什么样的牌(例如塔罗牌、法式套牌、德式套牌等)、玩家是谁等等,以及游戏应该遇到什么样的单子

现在假设我想定义一种类似于纸牌游戏的游戏。这些游戏是在两名玩家之间进行的,每个玩家在一个适合法国的纸牌游戏中获得一副牌,但游戏本身有许多变体,因此我无法提前指定确切的规则(即
游戏
的实例化)如何部分地专门化上面的类型类?编译器对以下内容感到不安:

data Suit=Heart | Diamond | spad | Club
数据值=整数|杰克|女王|国王
数据法语套件=卡套件值
类(游戏m)=>战争m在哪里
--以下行中出现语法错误:
数据卡m=法式套装——任何类似战争的游戏都必须在法式套装卡上进行
甲板1::甲板m
甲板2::甲板m
玩家1::玩家m
玩家2::玩家m
你认为我能做到这一点吗


PS:战争类游戏可以用任何类型的牌玩,但这只是一个例子。我更感兴趣的是如何实现我所追求的类型类的部分专门化。

对于数据族,您不能这样做:
Card m
对于distinct
m
m'
,必须是与
Card m'
不同的类型。这是因为数据族必须是内射的,就像常规类型构造函数一样

充其量,
卡m
卡m'
都可以同构于
法语套件

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

{-# 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

卡m~法语套件
约束强制子类中的类型相等。

初始问题提出了
数据类型类
问题。但是代码已经使用了
类型族
数据组m::*
内部类型类需要/是一个类型族

为了保持简单,您可以使用Player、Card、Deck的类型类进行初始原型设计,以了解事情的进展情况,这将使代码保持简单。当结构出现以感受压力点时,结构需要更巧妙的抽象,您可以根据自己的需要移动到类型族。但是,这种类型族的使用似乎很好

另外,请不要忘记,类型类/族可以通过以下方式“部分实例化”(如问题所述):

{-#最小funA | funB#-}

也就是说,您可以在类中包含更多/全部,但需要的定义最少。

非常感谢!你的代码编译得很好。在我的头脑中,我理解数据族的工作方式与类型类似families@AhmadB:数据族更像GADT的相反:对于GADT,如果在构造函数上匹配,则可以知道类型;而对于数据族,如果知道类型,则可以匹配构造函数。类型族是类型级函数,通常比多参数类型类(具有函数依赖关系)提供的更一般的类型级关系更方便。