[Haskell-beginners] Best practice for catching pure errors in IO?

Aleksandar Dimitrov aleks.dimitrov at googlemail.com
Wed Oct 27 16:06:04 EDT 2010


> I have a pure value which may throw errors when it is evaluated in an IO  
> monad. My code to handle it looks like this:

"Throwing" errors (i.e. calling the `error` function from pure code) isn't  
something you should do except in cases you're *very* sure that there is  
*no* way for the program to recover ever again. Maybe and Either are  
better alternatives. `try` basically just forces using the cleaner way of  
handling exceptions (Either) when you have to deal with something (say,  
library code) that you know may call `error` (Prelude's `head` does that  
when given an empty list) but you'd prefer to recover more gracefully. If  
you have written the code yourself and you *know* you can recover,  
throwing an exception and recovering with `try` is not pretty. That's  
good, you don't want it to be pretty, you want it to hurt, since it's not  
optimal. You should instead make your `evaluate` have a signature like  
this: Value -> Either ErrorMessage Result, where ErrorMessage is probably  
`type`d to String.

> temp <- try $ evaluate value
> case temp of
> 	Left (error::ErrorCall) -> scoldTheUserAndTryAgain
> 	Right correct -> doSomething
>
> Can this be done without the temporary variable, or with less plumbing?  
> I tried lifting either, but GHC was fussing about ambiguous types,  
> appending a signature to a pattern match is no longer allowed(?), and it  
> didn't look that much cleaner.

It can be done without a temporary variable. You can write a function like  
this (scoldTheUserWith and tryToObtainAResult should be in the IO Monad,  
of course!)

> treat :: (Either ErrorMessage Result) -> IO Result
> treat (Left e) = scoldTheUserWith e >> tryToObtainAResult
> treat (Right r) = return r

Be careful, the result types of treat have to be the same for Left and  
Right! Now do something like this, inside the IO monad:

> tryToObtainAResult :: IO Result
> tryToObtainAResult = do	result <- treat $ evaluate value
> 	doSomethingWith result

I hope this doesn't contain errors, I was too lazy to check it :-P Also,  
it can perhaps be done more gracefully.
HTH,
Aleks

PS: GHC might complain about appending a type signature in pattern matches  
if you have something like this:

> import Control.Monad (liftM)
> main = do r :: Integer <- liftM read getLine
>           print $ show r

You can fix line 2 in 2 ways:

> main = do r <- liftM read hGetLine :: IO Integer

Or compiling with -XScopedTypeVariables (or setting {-# LANGUAGE  
ScopedTypeVariables #-} as the very first line in your file.)


More information about the Beginners mailing list