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并改为实现。CardPlayerdata

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
4

2 回答 2

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记录字段用于获取特定于玩家类型的数据,并且在您的类约束中具有多态性的函数可以简单地对 type 的值进行操作CardPlayer a

尽管对于常规函数(如您的默认实现)可能更有意义hasBlackjackbusts除非您真的需要对不受二十一点标准规则影响的玩家进行建模。

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

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

但是,这种方法随着您嵌套这样的专业化而迅速变得笨拙。

我建议HasPoints完全放弃。它只有一个函数,它只是提取一个Int,所以任何HasPoints一般处理实例的代码都可以只使用Ints 并完成它。

于 2013-04-23T13:51:33.110 回答
6

一般来说,如果不进行类型检查 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
于 2013-04-23T05:56:37.640 回答