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 类型的偏特化类型。
您不能对数据系列执行此操作:对于不同的 m
和 m'
,Card m
必须是 Card 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
约束强制子类中的类型相等。
最初的问题提出了 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 中,但至少需要这些定义。
作为某种形式的实验,我试图为 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 类型的偏特化类型。
您不能对数据系列执行此操作:对于不同的 m
和 m'
,Card m
必须是 Card 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
约束强制子类中的类型相等。
最初的问题提出了 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 中,但至少需要这些定义。