haskelloptparse-applicative

How to code for mutually exclusive flags in Options.Applicative


I'm a Haskell newbie. As a learning exercise, I'm trying to port one of my Rust programs to Haskell. In Rust, I use the amazing clap package, and have come across Options.Applicative as a good-looking alternative. Here's an example:

import Options.Applicative
import Data.Semigroup ((<>))

data Sample = Sample
  { tod        :: Bool
  , pmc        :: Bool
  , tai        :: Bool
  }

sample :: Parser Sample
sample = Sample
      <$>  switch
          ( long "tod"
            <> short 'o'
            <> help "Convert from TOD" )
      <*> switch
          ( long "pmc"
            <> short 'p'
            <> help "Convert from PMC" ) 
      <*> switch
          ( long "tai"
            <> short 't'
            <> help "Set TAI mode" )

main :: IO ()
main = greet =<< execParser opts
  where
    opts = info (sample <**> helper) ( fullDesc )
greet :: Sample -> IO ()
greet (Sample a b c) = print [a,b,c]

Having got this far, I've hit a brick wall. I need to make the "tod" and "pmc" flags mutually exclusive. There's an example in the package README which uses <|>, but it's not for boolean flags, and I don't have a clue how to transform this.

Can anyone help, please?


Solution

  • Make one of pmc or tod be a computed value, and only store the other.

    data Sample = Sample
        { tod :: Bool
        , tai :: Bool
        }
    
    pmc = not . tod
    
    sample = Sample
        <$> (   flag' True  (short 'o')
            <|> flag' False (short 'p')
            <|> pure True -- what do you want to do if they specify neither?
            )
        <*> switch (short 't')
    

    Or perhaps there are actually three modes of operation. Then make both tod and pmc be computed fields.

    data Mode = TOD | PMC | Other deriving Eq
    
    data Sample = Sample
        { mode :: Mode
        , tai :: Bool
        }
    
    tod = (TOD==) . mode
    pmc = (PMC==) . mode
    
    sample = Sample
        <$> (   flag' TOD (short 'o')
            <|> flag' PMC (short 'p')
            <|> pure Other
            )
        <*> switch (short 't')