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?
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.