我正在Haskell用多个AI对手写一个卡片游戏的模拟。
我希望有一个像GameParameters -> [AIPlayer] -> GameOutcome
这样的主要功能。但是我想将主要功能看作是一个“库函数”,所以我可以编写一个新的AIPlayer来改变其他任何东西。
所以我考虑创建一个类型类AIPlayer,因此主函数变为AIPlayer a => GameParameters -> [a] -> GameOutcome
。但这只允许插入一种类型的AI。因此,要在一个游戏中插入多个AIPlayers,我需要定义一个包装类型
AIWrapper = P1 AIPlayer1 | P2 AIPlayer2 | ...
instance AIWrapper AIPlayer where
gameOperation (P1 x) = gameOperation x
gameOperation (P2 x) = gameOperation x
...
我对这种包装类型感到不满意,我觉得必须有比这更好的东西,或者我错了吗?
答案 0 :(得分:17)
听起来你可能正在前往Luke Palmer称之为existential type class antipattern的路上。首先,我们假设您有几种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}
如果您只有一个这样的值,这会起作用,但是如您所注意到的那样可能会导致您遇到问题。但类型课会给你带来什么?在您的示例中,您希望统一处理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
s的函数;您可以轻松地将所有内容放入列表中,因为[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的帖子所述,这实际上并没有给你带来什么,实际上会让你的生活更加复杂。因为它相当于更简单,没有类型的情况,所以没有优势;只有当你收尾的结构更复杂时才需要存在。