Search code examples
haskellmonadsparsecio-monad

Using a parsec parser in the IO monad


I've defined a parser using Parsec, which has type Parsec Text () a for some a. I've also got a "deal with this chunk" function, which writes the thing I've parsed to a file and has type a -> IO (). The file format that it's parsing means that we get back to "top level" reasonably frequently.

Is there a way to take my original parser and "lift it" into the IO monad? I'm imagining something with the following type signature:

liftParser :: Parsec Text () a -> (a -> IO ()) -> ParsecT Text () IO ()

where the first argument is the pure parser and the second is the "do something with the thing I parsed" function.

Obviously, I can bodge together what I need by redefining my original parser in IO too, but that means my unit tests look horrible, and it just feels like the wrong approach.

Also, I can't do something crazy like calling runParserT because that would drop the source position information - if there's an error on line 1000 of the input, I'd like the error message to say so.

So is there a way to do this and, if so, how? Also, is this a sensible thing to do? I imagine that I'm at least managing to avoid accumulating the output data. And, assuming that I manage something like this, should I expect Parsec to manage to discard the input data that it's already dealt with?


Solution

  • Just so this question can be marked as answered: luqui's comment above explains how to do it.

    The trick is to define all your parsers polymorphically, with type ParsecT Text () m (the definitions all end up looking like thing :: Monad m => Parsec Text () m MyType). Then you can instantiate with m the identity monad in the test-bench and equal to IO where they are used.