2013-04-23 97 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