Handling errors in Haskell
These are the four types of error handling that are standard and widely used in the Haskell world, as of 2014.
There're some other libraries like
Either, but where you don't care or know the type of the exception) and
control-monad-exception which implements a checked exception monad, etc. but the above ones are the standard ones seen in the wild.
An unexpected code path, one that rarely but can happen and can be handled if needs be. That's done by
catch. Typically caused by IO going wrong in some way, like the machine running out of swap and your program terminating, a file not existing, etc. Say you were writing a library to do things on reddit, you'd define an exception type in your API:
data RedditException = Couldn'tUpvote | CommentFailed | LoginFailed !Text | ConnectFailure !HttpError deriving (Show,Typeable) instance Exception RedditException login :: Details -> Reddit () login details = do code <- tryLogin details case code of (200,val) -> setLoginContext val (_,err) -> throw (LoginFailed err)
Then later you might write
try (login …) or
catch (login …) (\e -> …) to handle the exception, if needed. Another exception might be a connection failure.
Your program is broken and needs to be updated. Simplest case in point:
head (x:_) = x head  = error "empty list"
And then someone has
head ages and then in a case they didn't expect,
ages is empty. If you're trying to take the head of an empty list your program logic is simply broken. So it might be better instead to write:
case listToMaybe ages of Nothing -> defaultAge Just first -> first
These exceptions can be caught via
catch as above, e.g. to ensure uptime of a program, but ideally they should not happen in a healthy codebase.
An expected return value:
Either SomeError a The type indicates that an error is common, but doesn't mean your program is broke. Rather that some input value wasn't right. Typically used by parsers, consumers, that are pure and often error out.
data ParseError = ParseError !Pos !Text
So this type describes exactly what is going on:
runParser :: Parser a -> Text -> Either ParseError a
Take a parser of
a, some text to parse, and return either a parser error or a parsed
a. Typical usage would be:
main = do line <- getLine case runParser emailParser line of Right (user,domain) -> print ("The email is OK.",user,domain) Left (pos,err) -> putStrLn ("Parse error on " <> pos <> ": " <> err)
Or depending on the code one might opt instead to use a deconstructing function:
main = do line <- getLine either (putStrLn . ("Parse error: " <>) . show) (print . ("The email is OK.",)) (runParser emailParser line)
There is simply no value there. This isn't a problem in the system. It means you don't care why there isn't a value, or you already know.
lookup :: Eq a => a -> [(a,b)] -> Maybe b
That is, take some key
a that can be compared for equality, and a list of pairs where the first is the same type of the key
a and maybe return the
b of the pair, or nothing.
So one might pattern match on this:
case lookup name person of Nothing -> "no name specified" Just name -> "Name: " <> name
Or use a deconstructing function:
maybe "no name specified" ("Name: " <>) (lookup name person)
Again, depends on the code and the person writing it whether an explicit case is used. Often monads like
Maybe are composed to make a chain of possibly-nothing values:
lookup "height" profile >>= parseInt >>= flip lookup recommendedSizes
So lookup a height from a person's profile (might not exist), parse it as integer (might not parse), then use that as a key to lookup from a mapping list of age to clothes size: