Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do laziness and exceptions work together in Haskell?

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:

  1. Can one be sure that the catch function always catches all exceptions, regardless of the nature of it first argument?
  2. If not, is there a well-known way to deal with this kind of problems? Something like the 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:

  1. Use ($!) with return, use forceM on the first argument of catch, use the catchStrict function.
  2. I also noticed that sometimes people add some strictness to instances of their transformers.

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
like image 789
JJJ Avatar asked Jun 22 '12 03:06

JJJ


People also ask

How lazy evaluation works in Haskell?

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.

What does it mean that Haskell is lazy?

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.

Why is lazy evaluation useful in Haskell?

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.

What is strict evaluation in Haskell?

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.


1 Answers

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.

like image 62
David Miani Avatar answered Nov 10 '22 21:11

David Miani