2016-01-20 11 views
-1

Я хотел бы написать Hangman https://github.com/fokot/reactive-hangman/blob/master/src/Hangman.hs с просмотром списка действий пользователя как ленивый поток. Мои рекурсивные версии работает нормально (в коде runGameRecursively (newGameState «секретно»))Итерация IO действий и лень

я застрял на вопрос лени

updateGameState :: GameState -> IO GameState 
updateGameState gs = do 
    l <- getALetter gs 
    return $ updateState gs l 

ff :: (a -> Bool) -> [IO a] -> IO a 
ff f (i:is) = do 
    res <- i 
    if f res then return res else ff f is 

runGameInfinite :: GameState -> IO() 
runGameInfinite gs = 
    -- infinite lazy game loop 
    let repl = tail $ iterate (\x -> x >>= updateGameState) (return gs) :: [IO GameState] 
    in do 
    endState <- ff gameEnded repl 
    putStrLn $ showState endState 

main = runGameInfinite (newGameState "car") 

При запуске игры каждый шаг в РЕПЛ нужно пересмотреть все предыдущие, даже если они уже были. Я пытался играть с $! но не нашел правильного ответа, чтобы сделать это еще. Спасибо

+2

«Я не в состоянии это сделать» - это не вопрос. Вы включили одну строку кода, но даже сообщение об ошибке, которое вы получаете при запуске этого гипотетического кода. Если вы попробовали это, и это дало ошибку, почему бы не включить ошибку, чтобы спасти людей некоторое время? Вы должны изолировать свою проблему до кода, который достаточно мал, чтобы задать ваш вопрос. – user2407038

+0

Хорошо, я играл с ним и пошел дальше и редактировал вопрос. – user1698641

+1

Ну, теперь проблема очевидна - IO не ленится. Использование IO, как и у вас, безусловно, является антипаттерном. Если вы хотите, чтобы это работало с IO, вам придется использовать 'unsafeInterleaveIO', что, очевидно, ужасно. Лучше, чтобы вы удалили логику своей игры из IO, тогда желаемая семантика (т. Е. 'TakeWhile p someInfiniteList' и варианты) будет работать, как ожидалось. Вместо того, чтобы иметь 'updateGameState :: GameState -> IO GameState', вы должны иметь' updateGameState :: UserInput -> GameState -> GameState', а затем 'readUserInput :: IO [UserInput]' тривиально - просто читать карту. линии <$> getContents'. – user2407038

ответ

2

Я думаю, что схема использования iterate, чтобы сделать якобы чистый список операций ввода-вывода, является источником проблемы здесь. Ваш план состоит в том, чтобы обновить состояние по пользовательскому вводу, но рассмотреть последовательность состояний как поток, который вы можете «обрабатывать как список». Если я использую подлинный iterateM для создания правильных потоков вещей, тогда все идет так, как вы хотели, чтобы они пошли. Так что, если я добавляю импорт

import Streaming -- cabal install streaming 
import qualified Streaming.Prelude as S 

и после ваших основных определений написать что-то вроде

runGameInfiniteStream gs = S.print $ S.take 1 $ S.dropWhile (not . gameEnded) steps 
    where 
    steps :: Stream (Of GameState) IO() 
    steps = S.iterateM updateGameState (return gs) 

main :: IO() 
main = runGameInfiniteStream (newGameState "car") 

тогда я получаю

>>> main 
You have 5 lifes. The word is "___" 
Guess a letter: 
c 
You have 5 lifes. The word is "c__" 
Guess a letter: 
a 
You have 5 lifes. The word is "ca_" 
Guess a letter: 
r 
GameState {secretWord = "car", lives = 5, guesses = "rac"} 

Я думаю, что это именно та программа, которую вы предназначены, но с использованием правильная концепция потока, а не смешивание IO и списков сложным способом. Нечто подобное можно было бы сделать с pipes и conduit и аналогичными пакетами.


(Добавлено позже :)

Для потоковой передачи в состояния, соответствующих чистый список символьного (эмуляции результата, вводимый пользователя), вы можете просто использовать scan

pureSteps 
    :: (Monad m) => GameState -> [Char] -> Stream (Of GameState) m() 
pureSteps gs chars = S.scan updateState gs id (S.each chars) 

это в основном такой же, как Prelude.scanl, который также может использоваться (в чистом виде) для просмотра обновлений:

>>> S.print $ pureSteps (newGameState "hi") "hxi" 
GameState {secretWord = "hi", lives = 5, guesses = ""} 
GameState {secretWord = "hi", lives = 5, guesses = "h"} 
GameState {secretWord = "hi", lives = 4, guesses = "h"} 
GameState {secretWord = "hi", lives = 4, guesses = "ih"} 

>>> mapM_ print $ scanl updateState (newGameState "hi") "hxi" 
GameState {secretWord = "hi", lives = 5, guesses = ""} 
GameState {secretWord = "hi", lives = 5, guesses = "h"} 
GameState {secretWord = "hi", lives = 4, guesses = "h"} 
GameState {secretWord = "hi", lives = 4, guesses = "ih"} 

Чтобы просмотреть окончательное «выигрывающее» состояние, если оно существует, вы можете написать, например.

runPureInfinite 
    :: Monad m => GameState -> [Char] -> m (Of [GameState]()) 
runPureInfinite gs = S.toList . S.take 1 . S.dropWhile (not . gameEnded) . pureSteps gs 

-- >>> S.print $ runPureInfinite (newGameState "car") "caxyzr" 
-- [GameState {secretWord = "car", lives = 2, guesses = "rac"}] :>() 

и так далее.

+0

Спасибо за помощь Майкл, это именно то, что я хотел.У меня есть один смежный вопрос. Как я могу построить функцию для тестирования, где я просто передаю [Char] вместо IO. Я знаю, что могу использовать S.each и возвращать Stream, но я хочу, чтобы runGameInfiniteStream был как можно большим. – user1698641

+0

Я вынул функцию состояния обновления '' 'runGameInfiniteStream :: Monad m => (GameState -> m GameState) -> GameState -> Stream (S.Of GameState) m() runGameInfiniteStream updateGameStateFunction gs = S.take 1 $ S.dropWhile gameInProgress шаги где --steps :: Monad m => GameState -> (GameState -> m GameState) -> поток (S.Of GameState) m() steps = обновление S.iterateMGameStateFunction (return gs) '' 'Как я могу построить тот, который позволит мне пройти внутри [Char] и соответствующим образом обновит состояние. Я пытался использовать StateMonad для этого, но безуспешно. – user1698641

+0

Что-то l ike 'pureSteps gs chars = S.scan updateState gs id (S.each chars)' дает обновленные состояния, соответствующие списку символов. ('scan' усложняется дополнительным полем, которое соответствует библиотеке' foldl') – Michael

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

  • Нет связанных вопросов^_^