【问题标题】:Folding Over User Input in Haskell在 Haskell 中折叠用户输入
【发布时间】:2019-07-26 00:05:26
【问题描述】:

我是 Haskell 的新手,我一直在编写一个简单的刽子手游戏来适应这种语言。我将游戏状态存储在名为 GameState 的数据类型中。

data GameState = GameState
  { word :: String,
    blanks :: String,
    wrongGuesses :: [Char]
  } deriving Show

我还有一个函数可以更新输入字符的状态,还有一个函数可以检查游戏是否结束。

updateState :: GameState -> Char -> GameState
updateState state c
  | (c `elem` (word state)) = state { blanks = newBlanks }
  | otherwise = state { wrongGuesses = c:(wrongGuesses state)}
  where newBlanks = foldr replaceAt (blanks state) $ elemIndices c (word state)
        replaceAt i = (take i) <> (const [c]) <> (drop (i + 1))

isPlaying :: GameState -> Bool
isPlaying state = ('_' `elem` (blanks state)) && (length (wrongGuesses state) < 5)

给定一个字符列表,我可以折叠来模拟给定输入的游戏。

runGame :: GameState -> [Char] -> [GameState]
runGame initialState chars = takeWhile isPlaying $ scanl updateState initialState chars

我的问题是如何调整此代码以处理实际用户输入。我尝试过使用序列,但程序只是无休止地获取用户输入。

main = runGame (newState "secret") <$> (sequence $ repeat getChar)

有什么方法可以折叠 getChar 类似于 runGame 的工作原理并在每次用户输入后打印游戏状态?我想避免像 runGame 那样的显式递归,如果可能的话,我想坚持使用标准库函数。

【问题讨论】:

    标签: haskell io fold


    【解决方案1】:

    首先:为什么你的程序不起作用?问题是当您执行repeat getChar 时,会重复getChar 无限。然后你使用sequence;因为sequence是如何实现的,所以需要遍历整个列表才能返回。但这个名单是无限的!结果:无限循环。

    现在,让我们尝试解决这个问题。通常我在这种情况下做的第一件事就是忘记诸如sequence之类的函数,而只是将其编写为递归函数:

    main = runGameIO (newState "secret")
      where
        runGameIO :: GameState -> IO ()
        runGameIO curState = do
            input <- getChar
            let newState = updateState curState input
            if isPlaying newState        -- if still playing
                then runGameIO newState  -- then repeat with new state
                else return ()           -- else finish
    

    现在,有什么方法可以简化runGameIO?我现在看不到任何东西。当然,我们现在可以看到 sequence 不起作用:sequence 要求赋予它的每个项目都是独立的,即每个 monadic 动作不依赖于先前的 monadic 动作。 (这就是为什么您也可以使用Applicative 约束而不是Monad 约束来实现sequence。)但是这个问题的全部关于响应给定之前的状态!很明显sequence 是行不通的。

    接下来,让我们对 do-notation 进行脱糖,看看我们是否能发现任何东西:

    runGameIO :: GameState -> Io ()
    runGameIO curState =
        getChar >>= \input ->
        let newState = updateState curState input
        in if isPlaying newState then runGameIO newState else return ()
    

    嗯……还是什么都没有,真的。但这使得您可以在此处使用fmap 更加明显:

    runGameIO :: GameState -> Io ()
    runGameIO curState =
        fmap (updateState curState) getChar >>= \newState ->
        if isPlaying newState then runGameIO newState else return ()
    

    此时,通常有一些巧妙的技巧可以用来摆脱显式递归。但我在这里看不到任何事情可做。如果你不限制自己使用标准库,我会说使用 monad-loops 包中的 iterateUntilM,但你不能使用它。有时显式递归是最好的,这似乎是其中一种情况。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-08-30
      • 1970-01-01
      • 2023-04-09
      • 1970-01-01
      • 2019-02-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多