2

クラス X と Y が与えられた場合、互いのクラスのインスタンスを作成するための最も慣用的なアプローチは何ですか? 例 -

instance (X a) => Y a where ...
instance (Y a) => X a where ...

延長は避けたいです。また、これにより厄介な無限再帰が発生する可能性があることを認識しているため、同じことを達成し、比較的 DRY を維持するためのまったく異なるアプローチを受け入れます。以下に、私が抱えている正確な問題に関するいくつかのコンテキストを示します-

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
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

彼らの共通点は何がありますか?それらはすべて、最初の引数としてクラス パラメータ タイプの値を 1 つだけ取るため、そのような値を指定すると、各関数をそれに適用して、クラスの制約を気にすることなく、すべて同じ情報を取得できます。

したがって、適切で慣用的な 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 Playerandは、以前のand型とCardPlayer Dealer同等です。ここでのレコード フィールドは、プレーヤーの種類に特化したデータを取得するために使用されます。また、クラス制約を使用してポリモーフィックになる関数は、 type の値を単純に操作できます。PlayerDealerplayerCardPlayer a

ブラックジャックの標準ルールに影響されないプレイヤーを本当にモデル化する必要がない限り、通常の機能 (デフォルトの実装など) を使用する方が理にかなっているかもしれませんhasBlackjackbusts

このバージョンから、HasPointsクラスのインスタンスである必要がある非常に異なるタイプがある場合、クラスを単独で定義できるようになりましたが、その有用性には懐疑的です。または、同じ変換を適用して別のレイヤーを取得できます。

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

ただし、このアプローチは、このような特殊化をネストすると、すぐに扱いにくくなります。

完全に落とすことをお勧めHasPointsします。を抽出する関数が 1 つしかないIntため、インスタンスを一般的に処理するコードは、単にs をHasPoints使用するだけで済みます。Int

于 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 に答える