2012-09-03 6 views
7

私は複数のAIの相手とハスケルのカードゲームのシミュレーションを書いています。 私はのようなsometingの主な機能を持っていたいと思います。しかし、私は主な機能を "ライブラリ機能"と考えています。それで、何かを変更して新しいAIPlayerを書くことができます。ハスケルのプラグイン可能なAI

私はtypeclass AIPlayerを作成することを考えました。したがって、main関数はAIPlayer a => GameParameters -> [a] -> GameOutcomeになります。しかし、これは1つのタイプのAIのみが挿入されることを可能にする。だから、1試合で複数のAIPlayersを挿入するために、私はwrappertype

AIWrapper = P1 AIPlayer1 | P2 AIPlayer2 | ... 

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

を定義する必要があり、私はこのラッパー型と幸せを感じていないと、これよりももっと良いものがなければならないような気がします、あるいは私が間違っていますか?

+1

おそらくあなたが探しているのは、異種コレクションです。 http://www.haskell.org/haskellwiki/Heterogenous_collectionsの「Showable」の例を参照してください。 – ErikR

+2

については、https://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/を参照してください。異機種間のリストの必要性を良い設計に変換する方法。 –

答えて

17

Luke Palmerがexistential type class antipatternと名付けたものにあなたが向いているように聞こえます。まず、あなたはAI-演奏データ型のカップルを持っていると仮定してみましょう:

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} 

この意志:

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

は今、あなたはそれら型クラスの両方のインスタンス作成することにより、これらと連携したいですそのような価値が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プレーヤーを学び、生み出す方法について説明します。あなたのデータ型とそのインスタンスは単にAIPlayerを生成する関数になります。 [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]のです。しかし、上記のルーク・パーマーの記事が観察しているように、これは実際にあなたに何も買わず、実際にあなたの人生はより複雑になります。シンプルでノータイプのクラスのケースに相当するので、利点はありません。存在するのは、ラップアップしている構造がもっと複​​雑な場合にのみ必要です。

+3

+1とてもいい説明です! – Landei

+0

ありがとう、それはまさに私が探していた種類の説明です – Ingdas

関連する問題