The question is similar to this question. However, this one is about exceptions, not about lazy I/O.
Here is a test:
{-# LANGUAGE ScopedTypeVariables #-}
import Prelude hiding ( catch )
import Control.Exception
fooLazy :: Int -> IO Int
fooLazy m = return $ 1 `div` m
fooStrict :: Int -> IO Int
fooStrict m = return $! 1 `div` m
test :: (Int -> IO Int) -> IO ()
test f = print =<< f 0 `catch` \(_ :: SomeException) -> return 42
testLazy :: Int -> IO Int
testLazy m = (return $ 1 `div` m) `catch` \(_ :: SomeException) -> return 42
testStrict :: Int -> IO Int
testStrict m = (return $! 1 `div` m) `catch` \(_ :: SomeException) -> return 42
So I wrote two functions fooLazy
which is lazy and fooStrict
which is strict, also there is two tests testLazy
and testStrict
, then I try to catch division by zero:
> test fooLazy
*** Exception: divide by zero
> test fooStrict
42
> testLazy 0
*** Exception: divide by zero
> testStrict 0
42
and it fails in lazy cases.
The first thing that comes to mind is to write a version of the catch
function that force the evaluation on its first argument:
{-# LANGUAGE ScopedTypeVariables #-}
import Prelude hiding ( catch )
import Control.DeepSeq
import Control.Exception
import System.IO.Unsafe
fooLazy :: Int -> IO Int
fooLazy m = return $ 1 `div` m
fooStrict :: Int -> IO Int
fooStrict m = return $! 1 `div` m
instance NFData a => NFData (IO a) where
rnf = rnf . unsafePerformIO
catchStrict :: (Exception e, NFData a) => IO a -> (e -> IO a) -> IO a
catchStrict = catch . force
test :: (Int -> IO Int) -> IO ()
test f = print =<< f 0 `catchStrict` \(_ :: SomeException) -> return 42
testLazy :: Int -> IO Int
testLazy m = (return $ 1 `div` m) `catchStrict` \(_ :: SomeException) -> return 42
testStrict :: Int -> IO Int
testStrict m = (return $! 1 `div` m) `catchStrict` \(_ :: SomeException) -> return 42
it seems to work:
> test fooLazy
42
> test fooStrict
42
> testLazy 0
42
> testStrict 0
42
but I use the unsafePerformIO
function here and this is scary.
I have two questions:
catch
function always catches all exceptions, regardless of the nature of it first argument?catchStrict
function is suitable?UPDATE 1.
This is a better version of the catchStrict
function by nanothief:
forceM :: (Monad m, NFData a) => m a -> m a
forceM m = m >>= (return $!) . force
catchStrict :: (Exception e, NFData a) => IO a -> (e -> IO a) -> IO a
catchStrict expr = (forceM expr `catch`)
UPDATE 2.
Here is another 'bad' example:
main :: IO ()
main = do
args <- getArgs
res <- return ((+ 1) $ read $ head args) `catch` \(_ :: SomeException) -> return 0
print res
It should be rewritten like this:
main :: IO ()
main = do
args <- getArgs
print ((+ 1) $ read $ head args) `catch` \(_ :: SomeException) -> print 0
-- or
--
-- res <- return ((+ 1) $ read $ head args) `catchStrict` \(_ :: SomeException) -> return 0
-- print res
--
-- or
--
-- res <- returnStrcit ((+ 1) $ read $ head args) `catch` \(_ :: SomeException) -> return 0
-- print res
--
-- where
returnStrict :: Monad m => a -> m a
returnStrict = (return $!)
UPDATE 3.
As nanothief noticed, there is no guarantee that the catch
function always catch any exception. So one need to use it carefully.
Few tips on how to solve related problems:
($!)
with return
, use forceM
on the first argument of catch
, use the catchStrict
function.Here is an example:
{-# LANGUAGE GeneralizedNewtypeDeriving, TypeSynonymInstances, FlexibleInstances
, MultiParamTypeClasses, UndecidableInstances, ScopedTypeVariables #-}
import System.Environment
import Prelude hiding ( IO )
import qualified Prelude as P ( IO )
import qualified Control.Exception as E
import Data.Foldable
import Data.Traversable
import Control.Applicative
import Control.Monad.Trans
import Control.Monad.Error
newtype StrictT m a = StrictT { runStrictT :: m a } deriving
( Foldable, Traversable, Functor, Applicative, Alternative, MonadPlus, MonadFix
, MonadIO
)
instance Monad m => Monad (StrictT m) where
return = StrictT . (return $!)
m >>= k = StrictT $ runStrictT m >>= runStrictT . k
fail = StrictT . fail
instance MonadTrans StrictT where
lift = StrictT
type IO = StrictT P.IO
instance E.Exception e => MonadError e IO where
throwError = StrictT . E.throwIO
catchError m h = StrictT $ runStrictT m `E.catch` (runStrictT . h)
io :: StrictT P.IO a -> P.IO a
io = runStrictT
It is essentially the identity monad transformer, but with strict return
:
foo :: Int -> IO Int
foo m = return $ 1 `div` m
fooReadLn :: Int -> IO Int
fooReadLn x = liftM (`div` x) $ liftIO readLn
test :: (Int -> IO Int) -> P.IO ()
test f = io $ liftIO . print =<< f 0 `catchError` \(_ :: E.SomeException) -> return 42
main :: P.IO ()
main = io $ do
args <- liftIO getArgs
res <- return ((+ 1) $ read $ head args) `catchError` \(_ :: E.SomeException) -> return 0
liftIO $ print res
-- > test foo
-- 42
-- > test fooReadLn
-- 1
-- 42
-- ./main
-- 0
Lazy evaluation is a method to evaluate a Haskell program. It means that expressions are not evaluated when they are bound to variables, but their evaluation is deferred until their results are needed by other computations.
Haskell is a lazy language. This means that the evaluation of expressions is delayed until their values are actually needed. The opposite is eager evaluation, which is what most programming languages implement, like C, Java, and Python.
Lazy evaluation has advantages and disadvantages. It's easy to see some of the advantages. First, you get the computational benefit that any code you don't absolutely need is never computed. Another benefit is that you can define and use interesting structures such as an infinite list.
A new Strict language extension to Haskell aims to make it easier to use Haskell for code that is meant to be mostly strict, i.e., evaluated in a non-lazy manner. The feature was recently merged into GHC's git HEAD and will be included in GHC's next release.
Firstly (I'm not sure if you know this already), the reason the catch doesn't work with the lazy case is the
1 `div` 0
expression isn't evaluated until it is needed, which is inside the print
function. However, the catch
method is applied just to the f 0
expression, not the whole print =<< f 0
expression, so the exception isn't caught. If you did:
test f = (print =<< f 0) `catch` \(_ :: SomeException) -> print 42
instead, it works correctly in both cases.
If you want to make a catch statement though that forces complete evaluation of the IO result, instead of making a new instance of NFData, you could write a forceM
method, and use that in the catchStrict
method:
forceM :: (Monad m, NFData a) => m a -> m a
forceM m = m >>= (return $!) . force
catchStrict :: (Exception e, NFData a) => IO a -> (e -> IO a) -> IO a
catchStrict expr = (forceM expr `catch`)
(I'm a bit surprised that forceM isn't inside the Control.DeepSeq
library)
Regarding your comment:
No, the rule is the exception is only thrown when the value is computed, and that is only done when it is needed by haskell. And if haskell can delay the evaluation of something it will.
An example test function that doesn't use $!
, but still causes an exception straight away (so the normal catch will catch the divide by zero exception) is:
fooEvaluated :: Int -> IO Int
fooEvaluated m = case 3 `div` m of
3 -> return 3
0 -> return 0
_ -> return 1
Haskell is forced to evaluated the "3 `div` m" expression, as it needs to match the result against 3 and 0.
As a last example, the following doesn't throw any exception, and when used with the test function returns 1:
fooNoException :: Int -> IO Int
fooNoException m = case 3 `div` m of
_ -> return 1
This is because haskell never needs to calculate "3 `div` m" expression (as _
matches everything), so it is never calculated, hence no exception is thrown.
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