1

I'm dipping my toe into the Haskell pool, and I'm starting to get the hang of it. For the most part, the lack of traditional control structures doesn't bother me too much. (I'm coming from a C/C++ background.) But I'm a little confused about how you'd repeat an action. For example, if you have a turn-based game, in an imperative language, you might do something like this:

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

It's not clear to me how you'd do this in Haskell. You could do something recursive like:

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

But if you do that, don't you run the risk of a stack overflow? Or will the compiler take the tail-recursive definition and convert it into the equivalent of a for loop? If so, is this the accepted way of doing this sort of thing?

4

2 回答 2

6

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

对于 monads 中的循环,monad-loops包很不错。你的例子会写成

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

在哪里getStateputState表现得像getput来自State单子。

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

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

或者

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

请参阅避免显式递归以获取更多关于为什么显式递归应该用阳离子处理的信息。

于 2013-09-18T05:28:09.020 回答
3

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

作为进一步阅读,您可能会发现James Hague 的几篇关于函数式语言游戏的简短文章(他使用 Erlang 进行插图,但想法很笼统),以及对游戏编程的组件-实体-状态方法的描述由 Chris Granger(在 Clojure 中说明)。

于 2013-09-18T05:23:05.453 回答