7

Haskell で複数の AI 対戦相手とのカード ゲームのシミュレーションを書いています。のようなメイン関数が欲しいですGameParameters -> [AIPlayer] -> GameOutcome。しかし、メイン関数を「ライブラリ関数」と考えたいので、他のものを変更して新しい AIPlayer を作成できます。

そこで、型クラス AIPlayer を作成しようと考えたので、メイン関数は になりAIPlayer a => GameParameters -> [a] -> GameOutcomeます。ただし、これは 1 種類の AI しか挿入できません。1 つのゲームに複数の AIPlayer を挿入するには、wrappertype を定義する必要があります

AIWrapper = P1 AIPlayer1 | P2 AIPlayer2 | ...

instance AIWrapper AIPlayer where
     gameOperation (P1 x) = gameOperation x
     gameOperation (P2 x) = gameOperation x
     ...

私はこのラッパータイプに満足していません。これよりも優れたものがあるに違いないと感じていますか、それとも間違っていますか?

4

1 に答える 1

17

LukePalmerが実存型クラスのアンチパターンと呼んでいるものに向かっているようです。まず、AIを再生するデータ型がいくつかあると仮定しましょう。

data AIPlayerGreedy = AIPlayerGreedy { gName :: String, gFactor :: Double }
data AIPlayerRandom = AIPlayerRandom { rName :: String, rSeed   :: Int }

ここで、これらを型クラスの両方のインスタンスにすることで、これらを操作したいとします。

class AIPlayer a where
  name     :: a -> String
  makeMove :: a -> GameState -> GameState
  learn    :: a -> GameState -> a

instance AIPlayer AIPlayerGreedy where
  name     ai    = gName ai
  makeMove ai gs = makeMoveGreedy (gFactor ai) gs
  learn    ai _  = ai

instance AIPlayer AIPlayerRandom where
  name     ai    = rName ai
  makeMove ai gs = makeMoveRandom (rSeed ai) gs
  learn    ai gs = ai{rSeed = updateSeed (rSeed ai) gs}

これは、そのような値が1つしかない場合に機能しますが、気付いたように、問題が発生する可能性があります。しかし、型クラスはあなたに何を買いますか?あなたの例では、のさまざまなインスタンスのコレクションをAIPlayer均一に処理したいとします。コレクションに含まれる特定のタイプがわからないため、gFactorまたはrSeed;のようなものを呼び出すことはできません。によって提供されるメソッドのみを使用できるようになりますAIPlayer。したがって、必要なのはこれらの関数のコレクションだけであり、これらを単純な古いデータ型にパッケージ化できます

data AIPlayer = AIPlayer { name     :: String
                         , makeMove :: GameState -> GameState
                         , learn    :: GameState -> AIPlayer }

greedy :: String -> Double -> AIPlayer
greedy name factor = player
  where player = AIPlayer { name     = name
                          , makeMove = makeMoveGreedy factor
                          , learn    = const player }

random :: String -> Int -> AIPlayer
random name seed = player
  where player = AIPlayer { name     = name
                          , makeMove = makeMoveRandom seed
                          , learn    = random name . updateSeed seed }

つまりAIPlayer、は、その名前、行動を起こす方法、新しいAIプレーヤーを学び、生産する方法などのノウハウの集まりです。データ型とそのインスタンスは、単にAIPlayersを生成する関数になります。[greedy "Alice" 0.5, random "Bob" 42]よく型付けされているように、すべてを簡単にリストに入れることができます。それは型[AIPlayer]です。


あなた、それは本当です、あなたの最初のケースを実存的なタイプでパッケージ化することができます:

{-# LANGUAGE ExistentialQuantification #-}
data AIWrapper = forall a. AIPlayer a => AIWrapper a

instance AIWrapper a where
  makeMove (AIWrapper ai) gs = makeMove ai gs
  learn    (AIWrapper ai) gs = AIWrapper $ learn ai gs
  name     (AIWrapper ai)    = name ai

さて、[AIWrapper $ AIPlayerGreedy "Alice" 0.5, AIWrapper $ AIPlayerRandom "Bob" 42]よくタイプされています:それはタイプ[AIWrapper]です。しかし、上記のLuke Palmerの投稿が観察しているように、これは実際には何も購入せず、実際、あなたの生活をより複雑にします。これは、より単純な型クラスのない場合と同等であるため、利点はありません。実存は、まとめる構造がより複雑な場合にのみ必要です。

于 2012-09-04T00:05:23.753 に答える