Unexpected behavior when using state monad - State wrapped in a list

116 Views Asked by At

I am learning Haskell now, and I am trying to work with the state Monad.

I am recursively asking for input from the user and append it to the list. When I am implementing the IO handling and state execution in the same function, the program works as expected:

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 []

For reasons I don't understand, when I am separating the state handling function from the IO function, the returned state is wrapped with a list:

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 []

The following code gives me a compiler error that the actually returned type is a nested list:

  • Couldn't match type ‘[Char]’ with ‘Char’
  Expected type: [String]
    Actual type: [[String]]

Also, I can only flatten the array outside of changeState function, doing it inside the function would not work.

Anyone could explain why this is happening? and a better way of handling state in a recursive function?

1

There are 1 best solutions below

0
willeM_ Van Onsem On

You are using a do in and return in the changeState. Since the type of the do block is [String], it will make use of a the list instance of the Monad typeclass, and this thus means that return :: Monad m => a -> m a will wrap an element in a list.

You can implement you changeState function as:

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