Parece que podría estar en camino hacia lo que Luke Palmer denominó existential type class antipattern. En primer lugar, vamos a suponer que usted tiene un par de tipos de datos AI-playing:
data AIPlayerGreedy = AIPlayerGreedy { gName :: String, gFactor :: Double }
data AIPlayerRandom = AIPlayerRandom { rName :: String, rSeed :: Int }
Ahora, usted quiere trabajar con ellos haciéndoles ambas instancias de una clase de tipo:
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}
Esta voluntad funciona si solo tienes uno de esos valores, pero puedes causarte problemas, como habrás notado. Sin embargo, ¿qué te compra la clase de tipo? En su ejemplo, desea tratar una colección de diferentes instancias de AIPlayer
uniformemente. Como no sabe qué tipos de específicos estarán en la colección, nunca podrá llamar a nada como gFactor
o rSeed
; solo podrá utilizar los métodos provistos por AIPlayer
. Así que todo lo que necesita es una colección de esas funciones, y podemos empaquetar los en una llanura de edad tipo de datos:
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
, entonces, es una colección de conocimientos: su nombre, ¿cómo para hacer un movimiento, y cómo aprender y producir un nuevo jugador AI. Sus tipos de datos y sus instancias simplemente se convierten en funciones que producen AIPlayer
s; puede poner todo fácilmente en una lista, ya que [greedy "Alice" 0.5, random "Bob" 42]
está bien tipado: es del tipo [AIPlayer]
.
Usted puede , es verdad, el paquete de su primer caso con un tipo existencial:
{-# 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
Ahora, [AIWrapper $ AIPlayerGreedy "Alice" 0.5, AIWrapper $ AIPlayerRandom "Bob" 42]
está bien mecanografiadas-: es de tipo [AIWrapper]
. Pero como observa la publicación de Luke Palmer, esto en realidad no te compra nada, y de hecho hace tu vida más complicada. Dado que es equivalente al caso más simple, sin clase, no hay ventaja; los existenciales solo son necesarios si la estructura que está terminando es más complicada.
Quizás lo que está buscando es una colección heterogénea. Vea el ejemplo 'Showable' en http://www.haskell.org/haskellwiki/Heterogenous_collections – ErikR
Consulte https://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/ para una discusión de cómo transformar la necesidad de listas heterogéneas en un buen diseño. –