0

I have

type Month = Int

parseMonths :: OP.Parser (Month, Month)
parseMonths =
    liftA2 (,)
        (OP.option
            (OP.eitherReader $
             parseNumber "month" (\n -> 1<=n && n<=12) "month")
            (OP.metavar "MONTH" <>
             OP.long "from-month" <>
             OP.value 1))
        (OP.option
            (OP.eitherReader $
             parseNumber "month" (\n -> 1<=n && n<=12) "month")
            (OP.metavar "MONTH" <>
             OP.long "to-month" <>
             OP.value 12))

I want to add a check, that the first month is not after the second month. Obviously I cannot do that in OP.ReadM. Can I perform the check in OP.Parser? Or do I have to perform the check after parsing with parserFailure like here: Optparse-applicative: consecutive parsing (ReadM) ?

Lemming
  • 577
  • 6
  • 16

1 Answers1

0

Can I perform the check in OP.Parser?

No.

Parser is only an Applicative and not a Monad (hence the package name, optparse-applicative). This is a typical feature of parser functors - because the Applicative instance collects as many errors as possible, there is no possible lawful Monad instance.

Without being a Monad, it's impossible to write Parsers that depend on the result of other Parsers - since that's the defining feature of monads.


However, optparse-applicative does offer an escape hatch, if you really need one: use ParserM.

ParserM is basically an alternative to Parser which is a Monad - at the cost of dropping error messages some of the time. You can write a monadic parser using ParserM, and then use runParserM to turn it back into a regular Parser.

Isaac van Bakel
  • 1,772
  • 10
  • 22
  • `OP.Parser` being `Applicative` and not `Monad` only means that you cannot define `bind` or `join` of `Monad` class. However, in principle there could be a custom function like `checkMulti :: (a -> Maybe String) -> OP.Parser a -> OP.Parser a` that provides my wanted functionality. – Lemming Sep 17 '21 at 10:50