Haskell Monad Transformer Stack and Type Signatures?

There was many issues with your code snippet. I fixed your snippet adding explanation as to what was broken and added some style advice if you care.

Up vote 9 down vote favorite 3 share g+ share fb share tw.

I am attempting to create a stack of monad transformers and am having trouble getting the correct type signatures for my functions. (I'm still pretty new to Haskell) The stack combines multiple StateT transformers since I have multiple states I need to keep track of (two of which could be tupled, but I'll get to that in a second) and a WriterT for logging. Here's what I have so far: module Pass1 where import Control.Monad.

Identity import Control.Monad. State import Control.Monad. Writer import Data.

Maybe import qualified Data. Map as Map import Types data Msg = Error String | Warning String type Pass1 a = WriterT Msg (StateT Int (StateT Line (StateT Address Identity))) a runPass1 addrs instrs msgs = runIdentity (runStateT (runStateT (runStateT (runWriterT msgs) 1) instrs) addrs) --popLine :: (MonadState s m) => m (Maybe s) --popLine :: (Monad m) => StateT Line m (Maybe Line) popLine :: (MonadState s m) => m (Maybe Line) popLine = do ls do put xs return $ Just x -> return Nothing incLineNum :: (Num s, MonadState s m) => m () incLineNum = do ln m s curLineNum = do ln do ... -> return Nothing } In the definition of `popLine': popLine = do ls ... -> return Nothing } Pass1. Hs:22:0: Couldn't match expected type `s' against inferred type `Line' `s' is a rigid type variable bound by the type signature for `popLine' at Pass1.

Hs:21:23 When using functional dependencies to combine MonadState Line m, arising from a use of `get' at Pass1. Hs:23:14-16 MonadState s m, arising from the type signature for `popLine' at Pass1. Hs:(22,0)-(28,31) When generalising the type(s) for `popLine' Pass1.

Hs:23:14: Could not deduce (MonadState Line m) from the context (MonadState s m) arising from a use of `get' at Pass1. Hs:23:14-16 Possible fix: add (MonadState Line m) to the context of the type signature for `popLine' or add an instance declaration for (MonadState Line m) In a stmt of a 'do' expression: ls do ... -> return Nothing } In the definition of `popLine': popLine = do ls ... -> return Nothing } None of the signatures seem to be correct, but popLine is the first function so it's the only one that immediately causes an error. I try adding what it suggests in the type signature (eg: popLine :: (MonadState Line m) => ... but then it errors like so: Pass1.

Hs:21:0: Non type-variable argument in the constraint: MonadState Line m (Use -XFlexibleContexts to permit this) In the type signature for `popLine': popLine :: (MonadState Line m) => m (Maybe Line) I always seem to get this message whenever I try to do something that isn't a type variable. It seems to like (MonadState s m) ok and error on something else, but when I try it with a a instead of s it errors similar to the above. (Initially the Line and Int were tupled in a single state, but I was getting this error so I thought I'd try to put them in separate states).

GHC 6.10.4, Kubuntu So, can anyone tell me what is going on and give an explanation / show me the right type signatures, or does anyone know of a good reference on this stuff (the only thing that has helped so far was "Monad Transformers Step by Step", but that just uses one aux state function and one StateT)? Many Thanks in advance. Edit Here's the compiling code incorporating JFT's and Edward's suggestions: {-# LANGUAGE GeneralizedNewtypeDeriving #-} -- needed for: deriving (Functor,Monad) {-# LANGUAGE MultiParamTypeClasses #-} -- needed for: MonadState instance {-# LANGUAGE FlexibleContexts #-} -- needed for: (MonadState PassState m) => ... module Pass1 where import Control.Monad.

State import Control.Monad. Writer import Data. Maybe import Types type Lines = Line type Addresses = Address type LineNum = Int type Messages = Msg data Msg = Error String | Warning String data PassState = PassState { passLineNum :: LineNum , passLines :: Lines , passAddresses :: Addresses } newtype Pass1 a = Pass1 { unPass1 :: WriterT Messages (State PassState) a } deriving (Functor,Monad) instance MonadState PassState Pass1 where get = Pass1 .

Lift $ get put s = Pass1 . Lift $ put s runPass1 :: PassState -> Pass1 a -> ((a, Messages), PassState) runPass1 state = flip runState state . RunWriterT .

UnPass1 curLineNum :: (MonadState PassState m) => m LineNum curLineNum = do state m (Maybe Line) nextLine = do state do put state { passLines = xs, passLineNum = (c+1) } return $ Just x _ -> return Nothing evalr :: Pass1 (Maybe Line,LineNum) evalr = do l.

There was many issues with your code snippet. I fixed your snippet adding explanation as to what was broken and added some style advice if you care. Module Pass1_JFT where import Control.Monad.

Identity import Control.Monad. State import Control.Monad. Writer import Data.

Maybe import qualified Data. Map as Map {- replacing your import Types with simple definitions -} --import Types type Line = String type Address = String type LineNumber = Int {- Not part of your question but my 2 cents here... Say that you want to changes the collection for your states if you don't use a type alias you'll have to hunt everwhere you used it. Instead just change these definitions if required -} type Lines = Line type Addresses = Address type Messages = Msg data Msg = Error String | Warning String {- What is that Int in StateT Int?

Name it easier to read, reason about and to change. Declarative FTW let's use LineNumber instead -} --type Pass1 a = WriterT Msg (StateT Int (StateT Line (StateT Address Identity))) a {- Let's use a "real" type so instances can be derived. Since Pass1 is not a monad transfer i.e.

Not defined as Pass1 m a, no point using StateT for the deepest StateT i.e. StateT Address Identity so let's just use a State Address -} newtype Pass1 a = Pass1 { unPass1 :: WriterT Messages (StateT LineNumber (StateT Lines (State Addresses))) a } deriving (Functor,Monad) --runIdentity (runStateT (runStateT (runStateT (runWriterT msgs) 1) instrs) addrs) {- Let's peel that stack from the outermost (lefmost in the declaration) up to the innermost was Identity in your original declaration. Note that runWriterT does NOT take a starting state... The first parameter for runStateT (and runState) is not the initial state but the monad... so let's flip!

-} runPass1' :: Addresses -> Lines -> Messages -> Pass1 a -> ((((a, Messages), LineNumber), Lines), Addresses) runPass1' addrs instrs msgs = flip runState addrs . Flip runStateT instrs . Flip runStateT 1 .

RunWriterT . -- then get process the WriterT (the second outermost) unPass1 -- let's peel the outside Pass1 {- now that last function does NOT do what you want since you want to provide an initial log to append to with the WriterT. Since it is a monad transformer we'll do some trick here -} -- I keep the runStateT convention for the order of the arguments: Monad then state runWriterT' :: (Monad m,Monoid w) => WriterT w m a -> w -> m (a,w) runWriterT' writer log = do (result,log') Lines -> Messages -> Pass1 a -> ((((a, Messages), LineNumber), Lines), Addresses) runPass1 addrs instrs msgs = flip runState addrs .

Flip runStateT instrs . Flip runStateT 1 . Flip runWriterT' msgs .

-- then get process the WriterT (the second outermost) unPass1 -- let's peel the outside Pass1 {- Do you intend to call popLine directly from a Pass1 stack? If so you need to "teach" Pass1 to be a "MonadState Lines" To do so let's derive Pass1 (that's why we declared it with newtype! ) -} instance MonadState Lines Pass1 where -- we need to dig inside the stack and "lift" the proper get get = Pass1 .

Lift . Lift $ get put s = Pass1 . Lift .

Lift $ put s {- Better keep thing generic but we now could have written: popLine :: Pass1 (Maybe Line) -} popLine :: (MonadState Lines m) => m (Maybe Line) popLine = do ls do put xs return $ Just x -> return Nothing {- Ok now I get the Int => LineNumber.... we could make Pass1 and instance of MonadState LineNumber but LineNumber should not be messed with so instead I'd code the incLine directly and would provide a MonadReader instance for consulation if required check ":t incLineNum and :t curLineNum" -} incLineNum = Pass1 . Lift $ modify (+1) curLineNum = Pass1 $ lift get evalr = do l Haskell's type inference means that usually type annotations are superfluous (unless to remove ambiguity). In general the type we'd give to function is less generic that was is infer so it is better not to type annotate.

Type annotation is definitively a good debugging technique though ;) Cheers P.S. Real World Haskell chapter on Monad Transformer is excellent: http://book.realworldhaskell.org/read/monad-transformers.html.

5 Heroism should be rewarded. +1 – Norman Ramsey Jan 18 '10 at 5:03 Awesome, thank you VERY much! Step by step explanation is what I needed, thank you for taking the time to do that!

Your style suggestions were also well appreciated. Yes, RWH is a great book, I have my copy sitting in front of me right now. I think my problem is trying to read it too fast -- it's such a beautiful language, I'm impatient to learn it!

(btw, for anyone trying to run JFT's code, these ghc extensions need to be enabled:) {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} – paul Jan 18 '10 at 21:43 1 Also, neat trick putting your comments within {- and -}, I'll have to remember that when posting on message boards. Makes copying the code to run it super easy. – paul Jan 18 '10 at 21:44 Happy to be useful :) Sorry for the LANGUAGE directive I keep them in my .

Ghci and forgot about them. I typically used glasgow-exts and Arrows and BangPattern. – JFT Jan 18 '10 at 0:43.

In general you'll find that code winds up much clearer using one StateT with a larger composite structure for all of the bits of state that you need. One good reason is that when you come up with a piece of state you forgot you can always grow the structure by one field, and you can use the record sugar to write out single field updates or turn to something like the fclabels or data-accessor packages to manipulate state. Data PassState = PassState { passLine :: Int, passLines :: Line } popLine :: MonadState PassState m => m (Maybe Line).

PopLine = do state do put state { passLines = xs } return (Just x) _ -> return Nothing.

1 Good call putting all the state variables into a single data type, that hadn't occurred to me. (I'm still getting used to Haskell; for some reason I keep getting tunnel vision when thinking about it. ) I was thinking of putting them in a tuple but wanted to avoid the messy code that would cause.

Thus the stacked StateT's. But your suggestion clears that right up! Nice 'n' future proof, just as you said.

Thanks! – paul Jan 18 '10 at 21:52 1 When the bits of information you are tracking are related to the same concept it is indeed a very good idea (parsing state for exemple). On the other hand there are cases where you would prefer to keep the tracking orthogonal, hence more reusable.

My DSL at work tracks symbols, types assignation, environment, log and more. All are orthogonal so I have a monad stack to separate these concerns, and I actually reused part of the stack in various area of the system that don't need the "full" stack. – JFT Jan 19 '10 at 0:55 JFT: sure, the problem is that you wind up with an environment where you need to know the magic number of 'lifts' to get to your MonadState to do the right thing, or spend all your time messing with newtype noise.

The composite state approach can be extended by making a typeclass out of the fields you want to have present in more than one form of state or by taking the dual of the 'data types a la carte' approach and build up something like (StateT (Lines :*: LineCount)) m, but that becomes too much like OOHaskell for me to be entirely happy with it. – Edward Kmett Jan 19 '10 at 19:57.

I cant really gove you an answer,but what I can give you is a way to a solution, that is you have to find the anglde that you relate to or peaks your interest. A good paper is one that people get drawn into because it reaches them ln some way.As for me WW11 to me, I think of the holocaust and the effect it had on the survivors, their families and those who stood by and did nothing until it was too late.

Related Questions