Search code examples
haskellioicuparsec

How to nest Parser (IO a) while avoiding unsafePerformIO?


While playing around with parsing based on text-icu's BreakIterator, I've got stuck on implementing a function like this

conditionalParser :: (a -> Bool) -> Parser a -> Parser a -> Parser a -> Parser a
conditionalParser f a b c = do
        a' <- a
        if f a'
                then b
                else c

but with a type

conditionalParserIO :: (a -> Bool) -> Parser (IO a) -> Parser (IO a) -> Parser (IO a) -> Parser (IO a)

Is it possible without doing unsafePerformIO?

So far I could only get to some nested dos with the final returned type being Parser (IO (Parser (IO a))), but without any idea how to collapse them.


Solution

  • I think what you want is to use ParsecT instead of Parser.

    conditionalParserM :: Monad m => (a -> Bool) -> ParsecT s u m a -> ParsecT s u m a -> ParsecT s u m a
    conditionalParserM f a b c = do
        a' <- a
        if f a' then b else c
    

    This function works with all types of Monads, not just IO.

    I suppose it's possible to convert from a ParsecT s u IO a to a Parser (IO a) using runParsecT, depending on which Parser (this or this?) you're using. However, I would recommend that you just restructure your code to work with ParsecT instead.


    Clarification

    conditionalParserM can't be used as a replacement for conditionalParserIO. I'm suggesting that you need to change how your program works, because attempting to do what your doing (without unsafePerformIO, which you should almost never use) is impossible.

    You're looking to compose parsers based on the result of an IO operation, which means that the parser itself will perform side effects when it is run. In order to encapsulate this in the type, you need to use a monad transformer instead.

    So, to use conditionalParserM, you need to restructure your code to work with ParsecT instead of Parser.