I am porting a Java application to Haskell. The main method of the Java application follows the pattern:
public static void main(String [] args)
{
if (args.length == 0)
{
System.out.println("Invalid number of arguments.");
System.exit(1);
}
SomeDataType d = getData(arg[0]);
if (!dataOk(d))
{
System.out.println("Could not read input data.");
System.exit(1);
}
SomeDataType r = processData(d);
if (!resultOk(r))
{
System.out.println("Processing failed.");
System.exit(1);
}
...
}
So I have different steps and after each step I can either exit with an error code, or continue to the following step.
My attempt at porting this to Haskell goes as follows:
main :: IO ()
main = do
a <- getArgs
if (null args)
then do
putStrLn "Invalid number of arguments."
exitWith (ExitFailure 1)
else do
-- The rest of the main function goes here.
With this solution, I will have lots of nested if-then-else
(one for each exit point of the original Java code).
Is there a more elegant / idiomatic way of implementing this pattern in Haskell? In general, what is a Haskell idiomatic way to implement an early exit / return as used in an imperative language like Java?
Such a function does exist in Haskell. It's called exitImmediately, it lives in the unix package, and it calls out to the exit C library function. But not exitWith: it throws a runtime exception. There's a good reason for this exception-based behavior.
In this brief post I want to discuss a fairly unusual feature of Haskell - functions that can be parameterized by their return type. It's worth beginning with a quick discussion of the two most common kinds of compile-time polymorphism present in Haskell: parametric polymophism and ad-hoc polymorphism.
Since there is no scenario which is clearly better than the other, Haskell doesn't define Monoid for Int: Rather, it adds two new types that wrap Int: Sum and Product. Here's an example: As before with read, note how mempty returns a different type based on what's expected from it. Type inference picks the right overload!
In Haskell, the top-level mainfunction must have type IO (), so that programs are typically structured at the top level as an imperative-style sequence of I/O actions and calls to functional-style code. The functions exported from the IOmodule do not perform I/O themselves. They return I/O actions, which describe an I/O operation to be performed.
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
.
One way is to use the ErrorT
monad transformer. With it, you can treat it like a regular monad, return, bind, all that good stuff, but you also get this function, throwError
. This causes you to skip the following calculations either till you reach the end of the monadic computation, or when you call catchError. This is for error handling though, it's not meant to be for arbitrarily exiting a function in Haskell. I suggested it because it seems like that's what you're doing.
A quick example:
import Control.Monad.Error
import System.Environment
data IOErr = InvalidArgs String | GenErr String deriving (Show)
instance Error IOErr where
strMsg = GenErr --Called when fail is called
noMsg = GenErr "Error!"
type IOThrowsError = ErrorT IOErr IO
process :: IOThrowsError [String]
process = do
a <- liftIO getArgs
if length a == 0
then throwError $ InvalidArgs "Expected Arguments, received none"
else return a
main = do
result <- runErrorT errableCode
case result of
Right a -> putStrLn $ show a
Left e -> putStrLn $ show e
where errableCode = do
a <- process
useArgs a
now if process threw an error, useArgs wouldn't be executed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With