nebffa
nebffa

Reputation: 1549

Unwrapping the Haskell State Monad

In the process of writing an assignment for university I am having the ever-joyous fun of learning new Haskell monads. Yay!!!

I have a function that typechecks just fine:

compile :: Prog -> State VarsState String
compile prog@(Prog functions) = do
    s1 <- sequence (map (translate_func 0) [get_function prog name | name <- [func_name func | func <- functions]])
    return $ trace ("here's the program: \n" ++ show prog) $ concat $ s1

but when this other function:

maybe_compile_prog ::
    MaybeOK Prog -> String -> IO ()
maybe_compile_prog (Error msg) _ = do
    putStrLn ("error: " ++ msg)
maybe_compile_prog (OK prog) modulename = do
    s1 <- compile prog
    writeFile (modulename ++ ".m") ((header modulename) ++ s1)

tries to call it, it blows up at the line

s1 <- compile prog

saying it couldn't match the expected type "IO t0" with actual type "State VarsState String".

I assume this is because maybe_compile_prog returns type IO () so it expects to only unwrap IO information? VarsState is a custom datatype I have made to use with the State monad/

However, if that is the problem and I assume it is, I don't know how to transmit this simple string to maybe_compile_prog. Really, that's all I want to do - give a string to maybe_compile_prog.

Perhaps there's some neat way to unwrap this state monad? Perhaps it's possible to rewrite "compile" so that it takes in some state monad information whilst it runs, but then just returns a string (not wrapped in any monad)?

Please let me know if I'm missing any information.

Upvotes: 6

Views: 759

Answers (1)

Daniel Fischer
Daniel Fischer

Reputation: 183888

compile prog is an action in the State VarsState monad, thus you cannot use it in an IO-do-block as such. In a do-block, all lines must use the same monad, in this case IO.

You need to "run" the compile action to obtain the result, with one of

runState :: State s a -> s -> (a,s)
evalState :: State s a -> s -> a
execState :: State s a -> s -> s

depending on whether you need

  • both, result and final state
  • only result
  • only final state

In your case, you only want the result, so evalState it is.

For that you need to provide an initial state, it could look like

maybe_compile_prog (OK prog) modulename = do
    let s1 = evalState (compile prog) initialState
    writeFile (modulename ++ ".m") ((header modulename) ++ s1)

but then the provided initial state for the compile action would be the same for all OK progs passed. If that is not the right thing, you could pass the initial state as a parameter too.

Upvotes: 11

Related Questions