2013-04-23 98 views
2

给定类X和Y,创建对方类实例的最习惯方法是什么?例如 -非法实例声明/重叠实例

instance (X a) => Y a where ... 
instance (Y a) => X a where ... 

我想避免扩展。此外,我知道这可能会导致一些令人讨厌的无限递归,所以我打开一个完全不同的方法来完成相同的事情,并保持相对干燥。下面给出了一些背景为我有确切的问题 -

data Dealer = Dealer Hand 
data Player = Player Hand Cash 

class HasPoints a where 
    getPoints :: a -> Int 

class (HasPoints a) => CardPlayer a where 
    getHand :: a -> Hand 

    viewHand :: a -> TurnIsComplete -> Hand 

    hasBlackjack :: a -> Bool 
    hasBlackjack player = getPoints player == 21 && 
          (length . getCards . getHand) player == 2 

    busts :: a -> Bool 
    busts player = getPoints player > 21 

我想做到这一点 -

instance (CardPlayer a) => HasPoints a where 
    getPoints = getPoints . getHand 

但似乎我必须这样做 -

instance HasPoints Dealer where 
    getPoints = getPoints . getHand 

instance HasPoints Player where 
    getPoints = getPoints . getHand 

编辑

似乎我最喜欢的方法是保持HasPoints typeclass并执行CardPlayer作为data

data CardPlayer = Dealer Hand | Player Hand Cash 

instance HasPoints CardPlayer where 
    getPoints = getPoints . getHand 

getCash :: CardPlayer -> Maybe Cash 
getHand :: CardPlayer -> Hand 
viewHand :: CardPlayer -> TurnIsComplete -> Hand 
hasBlackjack :: CardPlayer -> Bool 
busts :: CardPlayer -> Bool 

-- I wanted HasPoints to be polymorphic 
-- so it could handle Card, Hand, and CardPlayer 

instance HasPoints Hand where 
    getPoints Hand { getCards = [] } = 0 

    getPoints hand = if base > 21 && numAces > 0 
        then maximum $ filter (<=21) possibleScores 
        else base 
     where base = sum $ map getPoints $ getCards hand 
      numAces = length $ filter ((Ace==) . rank) $ getCards hand 
      possibleScores = map ((base-) . (*10)) [1..numAces] 

instance HasPoints Card where 
    -- You get the point 
+0

但是你需要'HasPoints'作为一个单独的类吗?你不能只删除'HasPoints'并使'getPoints'成为一个带有'CardPlayer'约束的重载函数? – kosmikus 2013-04-23 07:54:18

+2

类型类在这里似乎没有任何用处;我怀疑你应该从这个模块中完全消除它们。为什么不只是'数据CardPlayer =经销商手|玩家手牌现金“然后像'getHand(经销商手)=手; getHand(玩家手现金)=手,然后'playerPoints = getPoints。 getHand'等等? – applicative 2013-04-23 13:39:38

+0

@applicative - 我喜欢'数据CardPlayer =经销商手|玩家手持现金“方法。但是,我想仍然使用HasPoints,所以getPoints函数可以是多态的。无论如何,你为什么不建议作为回答而不是评论? – pyrospade 2013-04-27 05:45:24

回答

7

鉴于X类和Y,创建对方课堂实例的最习惯方法是什么?

惯用的做法,给你的示例代码,是摆在首位不使用类型的类时,他们没有做什么有用的东西。考虑类功能的类型:

class HasPoints a where 
    getPoints :: a -> Int 

class (HasPoints a) => CardPlayer a where 
    getHand :: a -> Hand 
    viewHand :: a -> TurnIsComplete -> Hand 
    hasBlackjack :: a -> Bool 
    busts :: a -> Bool 

他们有什么共同点?它们都采用类参数类型的第一个值作为它们的第一个参数,因此给定这样一个值,我们可以将每个函数应用到它并获得所有相同的信息,而不需要打扰类约束。

所以,如果你想有一个漂亮的,惯用的干的做法,考虑一下:

data CardPlayer a = CardPlayer 
    { playerPoints :: Int 
    , hand :: Hand 
    , viewHand :: TurnIsComplete -> Hand 
    , hasBlackjack :: Bool 
    , busts :: Bool 
    , player :: a 
    } 

data Dealer = Dealer 
data Player = Player Cash 

在这个版本中,类型CardPlayer PlayerCardPlayer Dealer等同于PlayerDealer类型你了。这里的player记录字段用于获取专门针对玩家类型的数据,并且在您的类中约束多态的函数可以简单地对CardPlayer a类型的值进行操作。

尽管hasBlackjackbusts可能更有意义(比如你的默认实现),除非你真的需要模拟不受Blackjack标准规则影响的玩家。

从这个版本,你现在可以单独定义一个HasPoints类,如果你有非常不同的类型应该是它的实例,但我怀疑它的实用性,或者你可以应用相同的转换来获得另一个类型层:

data HasPoints a = HasPoints 
    { points :: Int 
    , pointOwner :: a 
    } 

但是,这种方法很快变得笨拙,你嵌套更深的专业化这样。我建议完全丢弃HasPoints。它只有一个函数,它只提取一个Int,所以一般处理HasPoints实例的任何代码都可以使用Int并完成它。

6

一般而言,it's impossible to declare all instances of a class to also be instances of another class without making type checking undecidable。所以,你提出的定义将只与UndecidableInstances工作启动:

{-# LANGUAGE FlexibleInstances, UndecidableInstances #-} 

instance (CardPlayer a) => HasPoints a where 
    getPoints = getPoints . getHand 

虽然可以走这条路,我建议重构的代码,而不是如下:

data Hand = ... 

handPoints :: Hand -> Int 
handPoints = ... 

data Dealer = Dealer Hand 
data Player = Player Hand Cash 

class CardPlayer a where 
    getHand :: a -> Hand 
    ... 

instance CardPlayer Dealer where ... 
instance CardPlayer Player where ... 

playerPoints :: (CardPlayer a) => a -> Int 
playerPoints = handPoints . getHand