Parsing command line options Here's is the code I'd like reviewed:

module Main where

import Control.Monad ( when )
import System.Exit ( exitSuccess )

import Idris.AbsSyntax
import Idris.Error
import Idris.CmdOptions
import Idris.Info
import Idris.Info.Show
import Idris.Package
import Idris.Main

import Util.System ( setupBundledCC )

processShowOptions :: [Opt] -&gt; Idris ()
processShowOptions opts = runIO $ do
  when (ShowAll `elem` opts)          $ showExitIdrisInfo
  when (ShowLoggingCats `elem` opts)  $ showExitIdrisLoggingCategories
  when (ShowIncs `elem` opts)         $ showExitIdrisFlagsInc
  when (ShowLibs `elem` opts)         $ showExitIdrisFlagsLibs
  when (ShowLibdir `elem` opts)       $ showExitIdrisLibDir
  when (ShowPkgs `elem` opts)         $ showExitIdrisInstalledPackages

check :: [Opt] -&gt; (Opt -&gt; Maybe a) -&gt; ([a] -&gt; Idris ()) -&gt; Idris ()
check opts extractOpts action = do
  case opt extractOpts opts of
    [] -&gt; return ()
    fs -&gt; do action fs
             runIO exitSuccess

processClientOptions :: [Opt] -&gt; Idris ()
processClientOptions opts = check opts getClient $ \fs -&gt; case fs of
  (c:_) -&gt; do
    setVerbose False
    setQuiet True
    case getPort opts of
      Just  DontListen       -&gt; ifail "\"--client\" and \"--port none\" are incompatible"
      Just (ListenPort port) -&gt; runIO $ runClient (Just port) c
      Nothing                -&gt; runIO $ runClient Nothing c

processPackageOptions :: [Opt] -&gt; Idris ()
processPackageOptions opts = do
  check opts getPkgCheck $ \fs -&gt; runIO $ do
    mapM_ (checkPkg opts (WarnOnly `elem` opts) True) fs
  check opts getPkgClean $ \fs -&gt; runIO $ do
    mapM_ (cleanPkg opts) fs
  check opts getPkgMkDoc $ \fs -&gt; runIO $ do
    mapM_ (documentPkg opts) fs
  check opts getPkgTest $ \fs -&gt; runIO $ do
    mapM_ (testPkg opts) fs
  check opts getPkg $ \fs -&gt; runIO $ do
    mapM_ (buildPkg opts (WarnOnly `elem` opts)) fs
  check opts getPkgREPL $ \fs -&gt; case fs of
    [f] -&gt; replPkg opts f
    _   -&gt; ifail "Too many packages"

-- | The main function for the Idris executable.
runIdris :: [Opt] -&gt; Idris ()
runIdris opts = do
  runIO setupBundledCC
  processShowOptions opts    -- Show information then quit.
  processClientOptions opts  -- Be a client to a REPL server.
  processPackageOptions opts -- Work with Idris packages.
  idrisMain opts             -- Launch REPL or compile mode.

-- Main program reads command line options, parses the main program, and gets
-- on with the REPL.
main :: IO ()
main = do
  opts &lt;- runArgParser
  runMain (runIdris opts)


I'd like to improve it. There are two main related problems.

This code uses exitSuccess for early exit. This then leads to a misleading piece of code in runIdris.

I'd prefer runIdris to look something like:

runIdris opts = do
  runIO setupBundledCC
  runIO execute (processShowOptions opts
             &lt;|&gt; processClientOptions opts
             &lt;|&gt; processPackageOptions opts
             &lt;|&gt; idrisMain opts)


I think I've worked out a way forward but would love to hear your thoughts.
This looks fine. There is some replication in processPackageOptions, but that's manageable. Your syntactical variant isn't possible, though, since Idris isn't an instance of Alternative. type Idris = StateT IState (ExceptT Err IO) would only be an instance of Alternative if Err was an instance of Monoid, which it isn't.

You could wrap Idris in another short-circuiting monad, for example Either ExitCode (Idris a) or a transformer variant, but you have to replace all runIO with liftIdrisIO or similar. That might be too much.

But I concur, the exitSuccess in check isn't that obvious and should be made more obvious at type level.