haskellhaskell-turtle

Composing ExitCodes in Turtle. Why is there no Monad/Monad Transformer instance?


I am writing a shell script in Haskell using turtle and would like to know best practices on composing commands that could fail.

Now I have a case expression staircase, like so:

runRemote :: MonadIO io => Text -> Text -> io ()
runRemote oldVersion' newVersion' = sh $ do
  mkdir "out"
  e1 <- shell ("command " <> oldVersion') empty
  case e1 of
    ExitFailure n -> cleanup 
    ExitSuccess -> do
      e2 <- shell ("command " <> newVersion') empty
      case e2 of
        ExitFailure n -> cleanup 
        ExitSuccess -> do
          curDir <- pwd
          cd (curDir <.> oldVersion')
          e3 <- shell ("command something else") empty
          case e3 of
           -- ...
           -- And so on...

If the case expression was expanding on a Maybe type, the solution would be to derive a Monad instance.

Is there a special reason the library author didn't already derive a Monad instance for ExitCode or is there a better way to do error handling for Haskell shell code?


Solution

  • One alternative is using (.&&.) and (.||.) from Turtle.Prelude.

    (.&&.) :: Monad m => m ExitCode -> m ExitCode -> m ExitCode

    Analogous to && in Bash

    Runs the second command only if the first one returns ExitSuccess

    (.||.) :: Monad m => m ExitCode -> m ExitCode -> m ExitCode

    Analogous to || in Bash

    Run the second command only if the first one returns ExitFailure

    They allow you to chain your commands like this (note that everything involved must return an ExitCode, including the cleanup):

    (command1 .&&. command2) .||. cleanup
    

    Or, if you need different cleanup actions in each case:

    (command1 .||. cleanup1) .&&. (command2 .||. cleanup2)
    

    By the way, it is worth noting that ExitCode is not defined by turtle but rather by base, in the System.Exit module.