使用状态 monad 时的意外行为 - 状态包裹在列表中

问题描述

我现在正在学习 Haskell,我正在尝试与状态 Monad 合作。

我递归地要求用户输入并将其附加到列表中。当我在同一个函数中实现 IO 处理和状态执行时,程序按预期工作:

picture_count

由于我不明白的原因,当我将状态处理函数与 IO 函数分开时,返回的状态是用一个列表包装的:

import Control.Monad.State

push :: String -> State [String] ()
push a = state $ \xs -> ((),a:xs)


testingState :: State [String] ()
testingState = do
  push ("testing state!")
  return ()


handleState :: [String]  -> IO()
handleState prevIoUsstack = do
  line <- getLine
  let userIntput = words line
  let stack = userIntput ++ prevIoUsstack
  let newStack = (execState testingState) stack
  handleState newStack


main :: IO 
main = do
  handleState []

以下代码给了我一个编译器错误,即实际返回的类型是嵌套列表:

handleState :: [String]  -> IO()
handleState prevIoUsstack = do
  line <- getLine
  let newStack = changeState line stack
  handleState newStack

changeState :: String -> [String] -> [String]
changeState line prevIoUsstack = do
  let userIntput = words line
  let stack = userIntput ++ prevIoUsstack
  let newStack = (execState testingState) stack
  return newStack


main = do
  handleState []

另外,我只能在 Couldn't match type ‘[Char]’ with ‘Char’ Expected type: [String] Actual type: [[String]] 函数外部展平数组,在函数内部这样做是行不通的。

谁能解释为什么会这样?以及在递归函数中处理状态的更好方法

解决方法

您在 do 中使用了 returnchangeState。由于 do 块的类型是 [String],它将使用 Monad 类型类的列表实例,这意味着 return :: Monad m => a -> m a 将包装一个元素在列表中。

您可以将 changeState 函数实现为:

changeState :: String -> [String] -> [String]
changeState line previousStack = execState testingState (words line ++ previousStack)