2013-09-18 2 views
1

Я окунаю свой палец в бассейн Хаскелла, и я начинаю его разглядывать. По большей части отсутствие традиционных контрольных структур меня не слишком беспокоит. (Я исхожу из фона 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

Это очень естественный способ кодировать это, да. Переполнение стека не произойдет - компилятор действительно делает то, что эффективно устраняет хвостовой вызов. –

ответ

3

Вы правы, если функция является хвостовой рекурсивной, она будет преобразована в цикл компилятором. И этот способ написания основного цикла - это действительно то, как люди обычно это делают.

В качестве дополнительного чтения, вы можете найти интересующую пару коротких сообщений на games in functional languages by James Hague (он использует Erlang для иллюстрации, но идеи в целом), а также описание Component-Entity-State approach to game programming Криса Грейнджер (проиллюстрированное в Clojure).

+0

Большое спасибо! Я проверю их. – user1118321

6

В Haskell мы стараемся не использовать явную рекурсию. Рекурсия - действительно большой молот, и для большинства проблем функции более высокого порядка обеспечивают немного более контролируемое решение. Вы код отлично, он хвост рекурсивный, но часто проще читать комбинаторный подход

Для петель в монадах пакет monad-loops приятный. Вы пример должен быть написан так

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

Где getState и putState ведут себя как get и put от State монады.

Или, если вы избегаете монады и просто проездом состояние вручную

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

или

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

См Avoid Explicit Recursion больше о том, почему явной рекурсии следует относиться с катионом.

+0

Я рассмотрю это. Я еще не добрался до монад. – user1118321

+0

@ user1118321 Тогда почему вы используете синтаксис 'do'? Это просто сахар для монад – jozefg

+1

@jozefg, потому что вы можете быть абсолютно опытными в использовании ввода-вывода с синтаксисом 'do', не имея понятия, что такое монады, если вы рассматриваете проблему в оперативном режиме. – kqr

Смежные вопросы