重复没有控制结构的动作

时间:2013-09-18 04:45:20

标签: haskell

我正在把我的脚趾浸入Haskell池中,我开始了解它。在大多数情况下,缺乏传统的控制结构并不会给我带来太多麻烦。 (我来自C / C ++背景。)但我对你如何重复一个动作感到有些困惑。例如,如果你有一个回合制游戏,用命令式语言,你可能会做这样的事情:

while (not somePlayerWon())
{
    getNextMove();
    updateGameState();
}

我不清楚你在Haskell中如何做到这一点。你可以做一些递归的事情,如:

playARound gameState = do
    nextMove <- getNextMove gameState
    newGameState <- updateGameState gameState nextMove
    if (not somePlayerWon newGameState)
        playARound newGameState
        else gameOver -- I realize this probably has to return something

但是如果你那样做,那么你是否存在堆栈溢出的风险?或者编译器是否会采用尾递归定义并将其转换为等效的for循环?如果是这样,这是否是接受这种做法的方式?

2 个答案:

答案 0 :(得分:6)

在Haskell中,我们尽量不使用显式递归。递归是一个非常大的问题,对于大多数问题,高阶函数提供了一个稍微更有控制的解决方案。您的代码非常精细,它的尾部递归,但它通常更容易阅读基于组合器的方法

对于monad中的循环,monad-loops包很好。你的例子将被写为

whileM_ (getState >>= somePlayerWon) $ do
    state <- getState
    move  <- getNextMove
    putState $ getNewState state move

getStateputState的行为与get monad中的putState相同。

或者,如果您要避免使用monad并手动传递状态

until somePlayerWon
  (\gameState -> nextGameState gameState (getNextMove gameState))
  gameState

flip (until somePlayerWon) gameState $ \gameState ->
     nextGameState gameState $ getNextMove gameState

有关为什么应使用阳离子处理显式递归的更多信息,请参阅Avoid Explicit Recursion

答案 1 :(得分:3)

你是对的,如果函数是尾递归的,它将被编译器转换成循环。这种编写主循环的方式确实是人们通常如何做到的。

作为进一步的阅读,您可能会在games in functional languages by James Hague上找到一些有趣的短篇帖子(他使用Erlang进行插图,但这些想法很通用),以及Chris Granger对Component-Entity-State approach to game programming的描述(在Clojure中说明)。