我正在把我的脚趾浸入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
循环?如果是这样,这是否是接受这种做法的方式?
答案 0 :(得分:6)
在Haskell中,我们尽量不使用显式递归。递归是一个非常大的问题,对于大多数问题,高阶函数提供了一个稍微更有控制的解决方案。您的代码非常精细,它的尾部递归,但它通常更容易阅读基于组合器的方法
对于monad中的循环,monad-loops包很好。你的例子将被写为
whileM_ (getState >>= somePlayerWon) $ do
state <- getState
move <- getNextMove
putState $ getNewState state move
getState
和putState
的行为与get
monad中的put
和State
相同。
或者,如果您要避免使用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中说明)。