Evan Sebastian
Evan Sebastian

Reputation: 1744

The correct way to do parsing with StateT

Consider the following faulty parser written as StateT.

type Parser a = StateT String Maybe a -- Maybe is chosen arbitrarily here.

oneDigit :: Parser Int
oneDigit = do
        (x : _) <- get
        return (read x :: Int)

It throws following error:

No instance for (MonadState [String] (StateT String Maybe))

However, that didn't happen when I change to say, oneChar :: Parser Char.

My conclusion is that you can only define a Parser a if your State is m a

Now my question is, how do I loose this coupling requirement?

Upvotes: 1

Views: 244

Answers (2)

shang
shang

Reputation: 24832

The type error is trying to tell you that your state needs to be a list of strings instead of a single string, because you use

(x : _) <- get

to get the first element of the list and then read it with read (which expects a String).

Since you only want to parse a single digit, you can change the function to

oneDigit :: Parser Int
oneDigit = do
        (x : _) <- get
        return (read [x] :: Int)

[x] creates a string from the single character (since String is just an alias for [Char]).

Note that you probably don't want to use read in a parser implementation because it will crash with invalid input. reads is a slightly better alternative that lets you handle error cases. For example:

oneDigit :: Parser Int
oneDigit = do
    (x : xs) <- get
    case reads [x] of
        [(n, "")] -> put xs >> return n
        _         -> lift Nothing

This also puts the rest of the string back to the state so that your parser actually consumes input when it succeeds.

Upvotes: 3

mariop
mariop

Reputation: 3226

Your state is a String, if you do (x : _) <- get then x is a Char. You can't use read on a Char. The error printed is due to the type inference that works the other way around: from read x the compiler infers that x is a String and that means that your state is [String], which is not true for Parser a. Specifically, the errors says that (StateT String Maybe) is not an instance of MonadState [String], that is true because it is an instance of MonadState String. You can see it from GHCI:

> import Control.Monad.State
> :i StateT
...
instance Monad m => MonadState s (StateT s m)

Where s == String in your case.

Now, if you want to read the next Char in your string as a digit (a num from 0 to 9), you need to replace read with digitToInt from Data.Char:

oneDigit :: Parser Int
oneDigit = do
    (x : _) <- get -- x will be inferred as Char
    return (digitToInt x :: Int)

Upvotes: 6

Related Questions