4

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?

duplode
  • 33,731
  • 7
  • 79
  • 150
Brent.Longborough
  • 9,567
  • 10
  • 42
  • 62
  • 5
    Why use booleans and not an enum that can have three values? – Willem Van Onsem Jul 08 '20 at 16:42
  • @WillemVanOnsem My original "design" envisaged booleans because they fitted better into the program logic: "if utc then" rather than "if thing = Utc then". – Brent.Longborough Jul 08 '20 at 17:14
  • 3
    @Brent.Longborough Pattern matching is a fundamental feature of Haskell. In fact, it is actually more fundamental (in Haskell) than `==`. You should take advantage of this. An added benefit is that this would make invalid states completely impossible to reach. It completely rules out an entire category of bugs (before the code is even run! No test-cases needed for that one). This idea of "making invalid states impossible" is an important concept in languages like Haskell. – David Young Jul 09 '20 at 01:54
  • 2
    @David Thank you. I'm very much a beginner, and I appreciate this kind of insight. -- From Daniel's solution, the elegance of this approach is dawning on me. Only a few years to go! – Brent.Longborough Jul 09 '20 at 09:23

1 Answers1

8

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')
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380