Es klingt, als ob Sie auf dem Weg zu dem sein könnten, was Luke Palmer existential type class antipattern nannte. Zuerst nehmen wir an, dass Sie ein paar KI-Spieldatentypen haben:
data AIPlayerGreedy = AIPlayerGreedy { gName :: String, gFactor :: Double }
data AIPlayerRandom = AIPlayerRandom { rName :: String, rSeed :: Int }
Jetzt wollen Sie mit diesen arbeiten, indem sie beide Instanzen einer Typklasse machen:
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}
Dieser Wille funktionieren, wenn Sie nur einen solchen Wert haben, aber können Sie Probleme verursachen, wie Sie bemerkt haben. Was kauft dir die Typklasse? In Ihrem Beispiel möchten Sie eine Sammlung verschiedener Instanzen von AIPlayer
einheitlich behandeln. Da Sie nicht wissen, welche spezifischen Typen in der Sammlung sein werden, können Sie nie etwas wie gFactor
oder rSeed
aufrufen; Sie können nur die von AIPlayer
bereitgestellten Methoden verwenden. Also alles, was Sie brauchen, ist eine Sammlung dieser Funktionen, und wir können diejenigen oben in einem einfachen alten Datentyp Paket:
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 }
Ein AIPlayer
, dann ist eine Sammlung von Know-how: seinen Namen, wie um einen Zug zu machen, und wie man einen neuen KI-Spieler lernt und produziert. Ihre Datentypen und ihre Instanzen werden einfach zu Funktionen, die AIPlayer
s ergeben; Sie können einfach alles in eine Liste eintragen, da [greedy "Alice" 0.5, random "Bob" 42]
gut typisiert ist: es ist vom Typ [AIPlayer]
.
Sie können, es ist wahr, Paket Ihren ersten Fall mit einer existentiellen Typ:
{-# 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
Nun [AIWrapper $ AIPlayerGreedy "Alice" 0.5, AIWrapper $ AIPlayerRandom "Bob" 42]
ist gut getippt: es ist vom Typ [AIWrapper]
. Aber wie Luke Palmers Beitrag oben bemerkt, kauft dir das eigentlich nichts und macht dein Leben sogar komplizierter. Da es dem einfacheren Fall der Nicht-Klassenklasse entspricht, gibt es keinen Vorteil. Existenzen sind nur notwendig, wenn die Struktur, die du einwickelst, komplizierter ist.
Vielleicht, was Sie suchen, ist eine heterogene Sammlung. Siehe das Beispiel 'Showable' unter http://www.haskell.org/haskellwiki/Heterogenic_collections – ErikR
Siehe https://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/ für eine Diskussion von Wie man eine Notwendigkeit für heterogene Listen in ein gutes Design umwandelt. –