非法实例声明/重叠实例

时间:2013-04-23 05:07:20

标签: haskell overlapping-instances

鉴于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类型类,并将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

2 个答案:

答案 0 :(得分: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

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

因此,如果你想要一个很好的,惯用的DRY方法,请考虑这个:

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并完成它。

答案 1 :(得分: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