How to use Control.Monad.State with Parsec?

I am surprised that I could not find information about this. I must be the only person who has problems with him.

So let's say I have a dash counter. I want it to count the number of dashes in a string and return the string. Imagine that I gave an example that will not work with parsec state processing. Therefore, this should work:

dashCounter = do str <- many1 dash count <- get return (count,str) dash = do char '-' modify (+1) 

And indeed, it compiles. Ok, so I'm trying to use it:

 :t parse dashCounter "" "----" parse dashCounter "" "----" :: (Control.Monad.State.Class.MonadState t Data.Functor.Identity.Identity, Num t) => Either ParseError (t, [Char]) 

Ok, that makes sense. It should return the state and string. Cool.

 >parse dashCounter "" "----" <interactive>:1:7: No instance for (Control.Monad.State.Class.MonadState t0 Data.Functor.Identity.Identity) arising from a use of `dashCounter' Possible fix: add an instance declaration for (Control.Monad.State.Class.MonadState t0 Data.Functor.Identity.Identity) In the first argument of `parse', namely `dashCounter' In the expression: parse dashCounter "" "----" In an equation for `it': it = parse dashCounter "" "----" 

Unfortunately. But then, how could it ever hope to work in the first place? Unable to enter initial state.

There is also a function:

 >runPT dashCounter (0::Int) "" "----" 

But it gives a similar error.

 <interactive>:1:7: No instance for (Control.Monad.State.Class.MonadState Int m0) arising from a use of `dashCounter' Possible fix: add an instance declaration for (Control.Monad.State.Class.MonadState Int m0) In the first argument of `runPT', namely `dashCounter' In the expression: runPT dashCounter (0 :: Int) "" "----" In an equation for `it': it = runPT dashCounter (0 :: Int) "" "----" 

It seems to me that I need to run RunState, or there should be a function that already does this inside, but I can not figure out where to go from here.

Edit: I had to specify more clearly, I did not want to use parsec state processing. The reason is that I have a feeling that I do not want his rollback to affect what he collects, with a problem that I am ready to solve.

However, Mr. McCann figured out how this should fit together, and the final code would look like this:

 dashCounter = do str <- many1 dash count <- get return (count,str) dash = do c <- char '-' modify (+1) return c test = runState (runPT dashCounter () "" "----------") 0 

Many thanks.

+8
haskell parsec
source share
3 answers

You actually have a lot of problems, all of which are relatively unobvious for the first time.

Starting with the simplest: dash returns () , which does not seem to be the way you want the results to be collected. You probably wanted something like dash = char '-' <* modify (+1) . (Note that I am using the operator from Control.Applicative here because it looks more neat)

Next, clearing up the confusion point: when you get a reasonable type signature in GHCi, pay attention to the context (Control.Monad.State.Class.MonadState t Data.Functor.Identity.Identity, Num t) . It doesn’t say what it is, it says that you want them to be. Nothing guarantees that the cases he asks for exist and, in fact, they do not. Identity not a state monad!

On the other hand, you are absolutely right in thinking that parse is meaningless; You cannot use it here. Consider its type: Stream s Identity t => Parsec s () a -> SourceName -> s -> Either ParseError a . As usual with monad transformers, Parsec is a synonym for ParsecT applied to monodar identity. Although ParsecT provides user state, you apparently do not want to use it, and ParsecT does not give an instance of MonadState . Here's the only matching instance: MonadState sm => MonadState s (ParsecT s' um) . In other words, to treat the parser as a state monad, you need to apply ParsecT to some other state monad.

This leads us to the following problem: Ambiguity. You use many type type methods and type signatures, so you are likely to come across situations where the GHC cannot know what type you really want, so you need to say that.

Now, as a quick fix, first define a type synonym to indicate the monad transformer stack name we want:

 type StateParse a = ParsecT String () (StateT Int Identity) a 

Give dashCounter appropriate type signature:

 dashCounter :: StateParse (Int, String) dashCounter = do str <- many1 dash count <- get return (count,str) 

And add the special function "run":

 runStateParse p sn inp count = runIdentity $ runStateT (runPT p () sn inp) count 

Now, in GHCi:

 Main> runStateParse dashCounter "" "---" 0 (Right (3,"---"),3) 

Also note that it is quite common to use newtype around the transformer stack instead of a simple type synonym. In some cases, this can help with ambiguity problems and obviously avoids ending up with giant type signatures.

+11
source share

If you want to use the user state component, Parsec offers a built-in function, you can use the monadic functions getState and modifyState .

I tried to stay true to my sample program, although using dash does not seem useful.

 import Text.Parsec dashCounter :: Parsec String Int (Int, [()]) dashCounter = do str <- many1 dash count <- getState return (count,str) dash :: Parsec String Int () dash = do char '-' modifyState (+1) test = runP dashCounter 0 "" "---" 

Note that runP really refers to your runState problem.

+7
source share

Although these answers fix this particular problem, they ignore the more serious problem with this approach. I would like to describe it here for anyone looking at this answer.

There is a difference between user state and the use of a StateT transformer. The internal state of the user is reset during backtracking, but StateT is not. Consider the following code. We want to add it to our counter if there is a dash and two if there is a plus. They give different results.

As you can see, both using the internal state and connecting the StateT transformer, you will get the correct result. The latter is due to the fact that you need to explicitly raise operations and be more careful with types.

 import Text.Parsec hiding (State) import Control.Monad.State import Control.Monad.Identity f :: ParsecT String Int Identity Int f = do try dash <|> plus getState dash = do modifyState (+1) char '-' plus = do modifyState (+2) char '+' f' :: ParsecT String () (State Int) () f' = void (try dash' <|> plus') dash' = do modify (+1) char '-' plus' = do modify (+2) char '+' f'' :: StateT Int (Parsec String ()) () f'' = void (dash'' <|> plus'') dash'' :: StateT Int (Parsec String ()) Char dash'' = do modify (+1) lift $ char '-' plus'' :: StateT Int (Parsec String ()) Char plus'' = do modify (+2) lift $ char '+' 

This is the result of running f, f 'and f' '.

 *Main> runParser f 0 "" "+" Right 2 *Main> flip runState 0 $ runPT f' () "" "+" (Right (),3) *Main> runParser (runStateT f'' 0) () "" "+" Right ((),2) 
+4
source share

All Articles