Absolutely forcing a catch of an Error in Haskell

exception, haskell

Solution

There are few things going wrong here. First, to force the evaluation of a value in IO, use `Control.Exception.evaluate`. This function has the type `evaluate :: a -> IO a`, and it's basically a hook to tell the compiler to force an evaluation. Its sole reason for existence is to enable exception handling.

Next, you need some machinery to actually catch the exception. You're currently using `handle`, but `Control.Exception.try`, which has the type `try :: Exception e => IO a -> IO (Either e a)`, is probably a bit simpler to use here. This type is slightly odd, but it means that, for some exception type you specify, it `try` will either return the value or an exception. If evaluation threw an exception that isn't the type you specified (and can't be coerced to it), `try` will re-throw the exception.

Calls to `error` produce an exception of the type `ErrorCall`, so to handle the exception you could use

sd :: BS.ByteString -> IO Bool
sd bs = do
        ie <- try $ evaluate $ Excel.isExcel bs
        either (const False) (id) (ie :: Either ErrorCall Bool)

of course you know that `isExcel` will only return `True`. You could use `toExcel` directly, and modify the `either` line to accommodate that.

As to the source of your problem,

a `seq` a

is exactly equivalent to `a`. It means, "at the time you evaluate (the second) `a`, evaluate (the first) `a` too". In other words, it's still too lazy. That's why you need `evaluate`.

Problem

I am using the hs-excelx library that itself uses zip-archive. zip-archive is reaching a condition in which it calls `fail`, which in that particular context, evaluates to a call to `error`. This is a call to `error` in pure code. I'm trying to detect whether a particular file is actually an Excel file. It's actually necessary for me to detect this without crashing, so I have written a function called isExcel to do the detection: ``` import qualified Data.Excelx as E isExcel :: BS.ByteString -> Bool isExcel = maybe False (\_ -> True) . E.toExcelx ``` Now, the catch is that this is a mere formality. If you call E.toExcelx on a bytestring that is not a zip archive, zip-archive will simply `error` out. But, I know that I'm calling `isExcel` in IO code, so I wrote an IO function to try to catch the error like this: ``` import qualified Data.ByteString.Lazy as BS import Control.Exception sd :: BS.ByteString -> IO Bool sd bs = handle handler $ do ie <- return $ Excel.isExcel bs return (ie `seq` ie) where handler :: SomeException -> IO Bool handler e = return False > sd BS.empty *** Exception: too few bytes. Failed reading at byte position 4 ``` What is going on? According to what I've read at http://www.haskell.org/haskellwiki/Error_vs._Exception and other places, I should be catching the exception and converting it to something useful. `ie` in my code might be a thunk to a Bool, but how can running `seq` on `ie` possibly leave anything unevaluated? How can I possibly catch an exception like this when I can't even figure out how to force the evaluation of a value? I don't have time to go in and hack proper error handling into zip-archive.

Original source