Il semble que vous pourriez être sur votre chemin à ce que Luc Palmer appelle le existential type class antipattern. Tout d'abord, supposons que vous avez deux types de données AI-jeu:
data AIPlayerGreedy = AIPlayerGreedy { gName :: String, gFactor :: Double }
data AIPlayerRandom = AIPlayerRandom { rName :: String, rSeed :: Int }
Maintenant, vous voulez travailler avec ceux-ci en les rendant les deux instances d'une classe de type:
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}
Cette volonté travailler si vous avez seulement une telle valeur, mais peut vous causer des problèmes, comme vous l'avez remarqué. Qu'est-ce que la classe de type vous achète, cependant? Dans votre exemple, vous souhaitez traiter uniformément une collection d'instances différentes de AIPlayer
. Puisque vous ne savez pas quels types spécifiques seront dans la collection, vous ne serez jamais en mesure d'appeler quelque chose comme gFactor
ou rSeed
; vous ne pourrez utiliser que les méthodes fournies par AIPlayer
. Donc, tout ce dont vous avez besoin est une collection de ces fonctions, et nous pouvons emballer les dans une plaine ancien type de données:
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 }
Un AIPlayer
, alors, est une collection de savoir-faire: son nom, comment faire un mouvement, et comment apprendre et produire un nouveau joueur IA. Vos types de données et leurs instances deviennent simplement des fonctions qui produisent AIPlayer
s; vous pouvez facilement mettre tout dans une liste, comme [greedy "Alice" 0.5, random "Bob" 42]
est bien typé: il est de type [AIPlayer]
.
Vous pouvez , il est vrai, package votre premier cas avec un type existentiel:
{-# 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
Maintenant, [AIWrapper $ AIPlayerGreedy "Alice" 0.5, AIWrapper $ AIPlayerRandom "Bob" 42]
est bien typé: il est de type [AIWrapper]
. Mais comme le remarque de Luke Palmer ci-dessus, cela ne vous achète rien et, en fait, vous complique la vie. Comme c'est l'équivalent du cas le plus simple, sans type de classe, il n'y a aucun avantage; Les existentiels ne sont nécessaires que si la structure que vous enveloppez est plus compliquée.
Peut-être ce que vous cherchez est une collection hétérogène. Voir l'exemple 'Showable' à http://www.haskell.org/haskellwiki/Heterogenous_collections – ErikR
Voir https://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/ pour une discussion sur comment transformer un besoin de listes hétérogènes en un bon design. –