Search code examples
parsinghaskellparsec

How can I do conditional monadic parsing using parsec?


Imagine the following example

data A = ...
data B = ...
data C = ...

convertA :: A -> C

parseA :: Parser A
parseB :: Parser B

parseC :: Parser C
parseC = do
  a <- parseA
  if parsed? a
    then return $ convertA a
    else parseB

Is there a way to implement such logic where I can try to use a parser, and if it is successful do some transformation on the result, and otherwise use another parser? I know this particular example could be written such as the following

parseC = (convertA <$> parseA) <|> parseB

but is there a more general way to represent this pattern in the monadic notation?


Solution

  • You can represent it more monadically, but I don't know if I'd call it a more general pattern.

    Normally, success or failure of a parser is handled implicitly through the MonadPlus and Alternative interfaces. However, you can reify success/failure and operate on it in the Monad context if you really want to. The function to do that reification is optionMaybe in Text.Parsec.Combinator.

    parseC :: Parser C
    parseC = do
        ma <- optionMaybe parseA
        case ma of
            Just a -> return $ convertA a
            Nothing -> parseB
    

    One important note here is that optionMaybe is.. special. It only succeeds with a Nothing result in the case when the parser provided to it fails without consuming input. Of course your example code is broken anyway if parseA can consume input while failing, so I assume you're familiar with that issue. This brokenness is why I hate parsec and will never use it in my own code, by the way. Sorry for the editorializing, I just don't think that issue is something every user should be forced to stumble over.