鉴于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
答案 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 Player
和CardPlayer Dealer
类型与您拥有的Player
和Dealer
类型相同。这里的player
记录字段用于获取专用于播放器类型的数据,并且在您的类中具有类约束的多态函数可以简单地对CardPlayer a
类型的值进行操作。
虽然将hasBlackjack
和busts
作为常规函数(比如默认实现)更有意义,但除非你真的需要模拟不受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