Haskell类型类的部分实例化
作为某种形式的实验,我试图为 Haskell 中的纸牌游戏定义一个非常通用的逻辑。为了保证最大的抽象级别,我定义了以下类型类:
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
这门课对我来说非常令人满意,因为它保留了将使用哪些卡(例如塔罗牌、法国花、德国花等)、玩家是谁等以及游戏应该运行什么样的底层单子的问题进入。
现在假设我想定义类似战争的纸牌游戏。这些游戏是在两个玩家之间进行的,每个玩家收到一副法式纸牌游戏,但是游戏本身有很多变体,所以我无法提前指定确切的规则是什么(即game) 我怎样才能部分特化上面的类型类?编译器对以下情况感到不安:
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:战争类游戏真的可以用任何类型的卡玩,但这只是一个例子。我对如何实现我所追求的类型类的部分专业化类型更感兴趣。
回答
你不能用数据族来做到这一点:Card m必须是一个不同的类型,Card m'用于不同的m和m'。这是因为数据族需要是单射的,就像常规类型构造函数一样。
充其量,Card m并且Card 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子类中的约束力量型的平等。
- @AhmadB: Data families are more like the converse of a GADT: with a GADT, if you match on the constructors, then you can know the type; whereas with a data family, if you know the type, then you can match on the constructors. Type families are type-level functions, which are often more convenient than the more general type-level relations offered by multi-parameter type classes (with functional dependencies).