【问题标题】:iterate IO actions and laziness迭代 IO 动作和惰性
【发布时间】:2016-04-26 22:41:07
【问题描述】:

我想编写 Hangman 游戏 https://github.com/fokot/reactive-hangman/blob/master/src/Hangman.hs,并将用户操作列表视为惰性流。我的递归版本工作正常(在代码中 runGameRecursively (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 中的每一步都需要重新评估所有之前的步骤,即使它们已经存在。我试着玩 $!但还没有找到正确的答案。谢谢

【问题讨论】:

  • “我做不到”不是问题 - 您已经包含了一行代码,但甚至没有包含运行此假设代码时收到的错误消息。如果您尝试过它并给出了错误,为什么不包括错误以节省人们一些时间呢?您应该将您的问题隔离到足够小的代码以放入您的问题
  • 好的,我玩了它并进一步编辑了问题。
  • 现在问题很明显 - I​​O 并不懒惰。像你一样使用 IO 肯定是一种反模式。如果您希望它与 IO 一起使用,则必须使用 unsafeInterleaveIO,这显然很糟糕。最好从 IO 中删除游戏的逻辑,然后所需的语义(即takeWhile p someInfiniteList 和变体)将按预期工作。而不是updateGameState :: GameState -&gt; IO GameState,您必须拥有updateGameState :: UserInput -&gt; GameState -&gt; GameState,然后readUserInput :: IO [UserInput] 是微不足道的——只需map read . lines &lt;$&gt; getContents

标签: haskell reactive-programming frp


【解决方案1】:

我认为使用iterate 制作表面上纯粹的 IO 操作列表的方案是这里问题的根源。您的计划是通过用户输入更新状态,但将状态的连续性视为您可以“像列表一样对待”的流。如果我使用真正的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 和列表。 pipesconduit 以及类似的包也可以做类似的事情。


(稍后添加:)

要流式传输到与纯字符列表相对应的状态(模拟来自用户输入的结果),您可以使用 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"}] :> ()

等等。

【讨论】:

  • 感谢迈克尔的帮助,这正是我想要的。我有一个相关的问题。我如何构造函数来测试我将通过 [Char] 而不是 IO 的地方。我知道我可以使用 S.each 并返回 Stream,但我希望 runGameInfiniteStream 尽可能大。
  • 我取出了更新状态函数runGameInfiniteStream :: Monad m =&gt; (GameState -&gt; m GameState) -&gt; GameState -&gt; Stream (S.Of GameState) m () runGameInfiniteStream updateGameStateFunction gs = S.take 1 $ S.dropWhile gameInProgress steps where --steps :: Monad m =&gt; GameState -&gt; (GameState -&gt; m GameState) -&gt; Stream (S.Of GameState) m () steps = S.iterateM updateGameStateFunction (return gs)我如何构造一个允许我在[Char]内部传递并相应更新状态的函数。我尝试为此使用 StateMonad,但没有成功。
  • 类似pureSteps gs chars = S.scan updateState gs id (S.each chars) 的东西会产生对应于字符输入列表的更新状态。 (scan 被一个额外的字段复杂化以适应 foldl 库)
  • 然后像runPure txt inputs = S.toList $ S.take 1 $ S.dropWhile (not . gameEnded) $ pureSteps (newGameState txt) inputs 这样的东西会给出一个包含最终结果的 0 或 1 成员列表,给定一个字符列表作为“输入”。所以如果我在 ghci 中写runPure "car" "cxar",我会得到[GameState {secretWord = "car", lives = 4, guesses = "rac"}] :&gt; ()
猜你喜欢
  • 2023-04-03
  • 1970-01-01
  • 2011-01-16
  • 2012-09-20
  • 2014-08-02
  • 1970-01-01
  • 1970-01-01
  • 2012-01-03
  • 1970-01-01
相关资源
最近更新 更多