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.
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
.
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