Given class X and Y, what's the most idiomatic approach to creating instances of each other's class? e.g. -
instance (X a) => Y a where ...
instance (Y a) => X a where ...
I'd like to avoid extensions. Also, I am aware that this could cause some nasty infinite recursion, so I'm open to a completely different approach to accomplish the same thing and stay relatively DRY. Below gives some context as to the exact problem I am having -
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
I'd like to do this -
instance (CardPlayer a) => HasPoints a where
getPoints = getPoints . getHand
But it seems I must do this -
instance HasPoints Dealer where
getPoints = getPoints . getHand
instance HasPoints Player where
getPoints = getPoints . getHand
EDIT
Seems my favorite approach is to keep the HasPoints
typeclass and implement CardPlayer
as data
instead.
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
Given class X and Y, what's the most idiomatic approach to creating instances of each other's class?
The most idiomatic approach, given your example code, is to not use type classes in the first place when they're not doing anything useful. Consider the types of the class functions:
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
What do they have in common? They all take exactly one value of the class parameter type as their first argument, so given such a value we can apply each function to it and get all the same information without needing to bother with a class constraint.
So if you want a nice, idiomatic DRY approach, consider this:
data CardPlayer a = CardPlayer
{ playerPoints :: Int
, hand :: Hand
, viewHand :: TurnIsComplete -> Hand
, hasBlackjack :: Bool
, busts :: Bool
, player :: a
}
data Dealer = Dealer
data Player = Player Cash
In this version, the types CardPlayer Player
and CardPlayer Dealer
are equivalent to the Player
and Dealer
types you had. The player
record field here is used to get the data specialized to the kind of player, and functions that would have been polymorphic with a class constraint in yours can simply operate on values of type CardPlayer a
.
Though perhaps it would make more sense for hasBlackjack
and busts
to be regular functions (like your default implementations), unless you really need to model players who are immune to the standard rules of Blackjack.
From this version, you can now define a HasPoints
class alone if you have very different types that should be instances of it, though I'm skeptical of the utility of that, or you can apply the same transformation to get another layer:
data HasPoints a = HasPoints
{ points :: Int
, pointOwner :: a
}
However, this approach quickly becomes unwieldy the further you nest specializations like this.
I would suggest droping HasPoints
entirely. It only has one function, which just extracts an Int
, so any code that handles HasPoints
instances generically might as well just use Int
s and be done with it.