在Haskell上,如何使用IO将AI控件作为一个函数(或者,如何正确地重新设计它)?

时间:2015-03-09 22:10:51

标签: haskell design-patterns io functional-programming

假设您必须定义一个取决于特定决策的过程。例如:

sendMonsterToGraveyard :: Game -> IO Game
sendMonsterToGraveyard game = do
    let monsters = monstersInPlay game
    monster <- choose monsters
    sendToGraveyard game monster

此功能是假想游戏的“效果”,让玩家选择一个怪物杀死。该设计的问题在于我们将效果耦合到IO monad。如果,稍后我们决定我们希望AI使用该效果(并因此选择一个怪物来杀死),该怎么办?除了让AI知道终端之外,这是不可能的,但这听起来并不健全。那么,什么是重新设计这种模式的正确方法,以便可以编码游戏效果而不将其专门与IO monad耦合?

注意:我是根据要求提出此问题作为我上一个问题的后续跟进。其中一个答案实际上为这个问题提供了一个很好的解决方案,但是,由于没有在问题上专门解决,我们认为最好创建一个新的。

2 个答案:

答案 0 :(得分:2)

IO留在那里并不是那么疯狂;它也是获取随机数的最简单方法。

您需要做的是将choose(以及需要的任何其他内容)作为参数传递到函数中,以便接受您未指定的参数choose :: Game -> [Monster] -> IO Monster预先。您甚至可以将它们指定为自己的newtype

newtype MonsterChooser = MonsterChooser (Game -> [Monster] -> IO Monster)

userMonsterChooser = MonsterChooser $ \_ monsters -> loopUntilJust $ do
    let indices = zip ['a'..'z'] monsters
    putStrLn "Choose a monster to target:"
    putStrLn $ unlines $ map (\(i, monster) -> i : '.' : ' ' : show monster) indices
    index <- getLine
    return $ lookup (trim (lowercase index)) indices

loopUntilJust :: (Monad m) => m (Maybe x) -> m x
loopUntilJust mmx = do
    mx <- mmx
    case mx of Nothing -> loopUntilJust mmx
               Just x  -> return x

这是一个更复杂的例子,允许你指定一个MonsterChooser。在选择怪物之前,AI会查看整个Game;用户只被问到他们想做什么。然后sendMonsterToGraveyard :: MonsterChooser -> Game -> IO Monster足够通用,您无需担心。

答案 1 :(得分:1)

这是使用DSL的好地方。

基本上,您可以定义您的操作并为这些操作编写不同的解释器,您应该得到类似的结果:

chooseMonster :: Game Monster 
chooseMonster = do
  monsters <- monstersInPlay
  monster <- chooseMonster
  sendToGraveyard monster

interpretPlayer = interpret game go
  where go action = case action of
    chooseMonster -> getMonsterFromUser
    ...

interpretAI = interpret game go
  where go action = case action of
    chooseMonster -> calculateBestMonster
    ...

实际上,IO也是一回事,唯一的区别是Haskell的运行时系统会解释IO动作。就像Game是一项操作一样,interpretPlayerinterpretAI是执行这些操作的不同方式。

freeoperational软件包提供了一种简单的方法来创建类似这样的DSL。另外,您可以查看https://softwareengineering.stackexchange.com/questions/242795/what-is-the-free-monad-interpreter-pattern