迭代 IO 操作和惰性
iterate IO actions and laziness
我想编写 Hangman 游戏 https://github.com/fokot/reactive-hangman/blob/master/src/Hangman.hs,将用户操作列表视为惰性流。我的递归版本工作正常(代码 运行GameRecursively (newGameState "secret"))
我被懒惰问题困住了
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")
当您 运行 游戏时,repl 中的每一步都需要重新评估之前的所有步骤,即使它们已经存在。我试着玩 $!但还没有找到正确的答案 ho 去做。谢谢
我认为使用 iterate
制作一个表面上纯粹的 IO 操作列表的方案是问题的根源。您的计划是通过用户输入更新状态,但将状态的连续性视为您可以 'treat like a list' 的流。如果我使用真正的 iterateM
来产生适当的流东西,那么事情就会完全按照你希望的那样进行。所以如果我添加 imports
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"}
查看最终的'winning'状态,如果存在的话,可以这样写,例如
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"}] :> ()
等等。
我想编写 Hangman 游戏 https://github.com/fokot/reactive-hangman/blob/master/src/Hangman.hs,将用户操作列表视为惰性流。我的递归版本工作正常(代码 运行GameRecursively (newGameState "secret"))
我被懒惰问题困住了
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")
当您 运行 游戏时,repl 中的每一步都需要重新评估之前的所有步骤,即使它们已经存在。我试着玩 $!但还没有找到正确的答案 ho 去做。谢谢
我认为使用 iterate
制作一个表面上纯粹的 IO 操作列表的方案是问题的根源。您的计划是通过用户输入更新状态,但将状态的连续性视为您可以 'treat like a list' 的流。如果我使用真正的 iterateM
来产生适当的流东西,那么事情就会完全按照你希望的那样进行。所以如果我添加 imports
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"}
查看最终的'winning'状态,如果存在的话,可以这样写,例如
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"}] :> ()
等等。