A slightly more sensible approach in Haskell that uses the same sort of conditional logic you tried might look like this:
fallOverAndDie :: String -> IO a
fallOverAndDie err = do putStrLn err
exitWith (ExitFailure 1)
main :: IO ()
main = do a <- getArgs
case a of
[d] | dataOk d -> doStuff $ processData d
| otherwise -> fallOverAndDie "Could not read input data."
_ -> fallOverAndDie "Invalid number of arguments."
processData r
| not (resultOk r) = fallOverAndDie "Processing failed."
| otherwise = do -- and so on...
In this particular case, given that exitWith
terminates the program anyway, we could also dispense with the nested conditionals entirely:
main :: IO ()
main = do a <- getArgs
d <- case a of
[x] -> return x
_ -> fallOverAndDie "Invalid number of arguments."
when (not $ dataOk d) $ fallOverAndDie "Could not read input data."
let r = processData d
when (not $ resultOk r) $ fallOverAndDie "Processing failed."
Using the same fallOverAndDie
as before. This is a much more direct translation of the original Java.
In the general case, the Monad
instance for Either
lets you write something very similar to the latter example above in pure code. Starting from this instead:
fallOverAndDie :: String -> Either String a
fallOverAndDie = Left
notMain x = do a <- getArgsSomehow x
d <- case a of
-- etc. etc.
...the rest of the code is unchanged from my second example. You can of course use something other than just String
as well; to more faithfully recreate the IO
version, you could use Either (String, ExitCode)
instead.
Additionally, this use of Either
is not limited to error handling--if you have some complicated calculation returning a Double
, using Either Double Double
and the same monadic style as above, you can use Left
to bail out early with a return value, then wrap the function using something like either id id
to collapse the two outcomes and get a single Double
.