Exception: Difference between revisions
(how to escape from Exception monad by handling the exception) |
(Niklaus Wirth and the exceptions) |
||
Line 14: | Line 14: | ||
See the implementation in <hask>Control.Monad.Error</hask> (and please, excuse the misleading name, for now). | See the implementation in <hask>Control.Monad.Error</hask> (and please, excuse the misleading name, for now). | ||
First for non-monadic functions. | There is an old dispute between C++ programmers on whether exceptions or error return codes are the right way. | ||
Also Niklaus Wirth considered exceptions to be the reincarnation of GOTO and thus omitted them in his languages. | |||
Now Haskell solves the problem the diplomatic way: | |||
Function return error codes, but the handling of error codes does not uglify the calling code. | |||
First we implement exception handling for non-monadic functions. | |||
Since no IO functions are involved, we can still not handle exceptional situations induced from outside the world, | |||
but we can handle situations, where it is unacceptable for the caller to check a priori whether the call can succeed. | |||
<haskell> | <haskell> | ||
data ExAction e a = | data ExAction e a = | ||
Line 34: | Line 41: | ||
</haskell> | </haskell> | ||
Now we extend this monadic functions. | Now we extend this to monadic functions. | ||
This is not restricted to IO, but may also | This is not restricted to IO, but may be used immediately also for non-deterministic algorithms implemented with the <hask>List</hask> monad. | ||
<haskell> | <haskell> | ||
newtype ExActionT e m a = | newtype ExActionT e m a = | ||
Line 73: | Line 80: | ||
Here are some | Here are some examples for typical IO functions with explicit exceptions. | ||
<haskell> | <haskell> | ||
data IOException = | data IOException = | ||
Line 97: | Line 104: | ||
</haskell> | </haskell> | ||
Finally we can escape from the Exception monad if we handle the | Finally we can escape from the Exception monad if we handle the exceptions completely. | ||
<haskell> | <haskell> | ||
main :: IO () | main :: IO () |
Revision as of 15:33, 23 January 2008
An exception denotes an unpredictable situation at runtime, like "out of disk storage", "read protected file", "user removed disk while reading", "syntax error in user input".
These are situation which occur relatively seldom and thus their immediate handling would clutter the code which should describe the regular processing.
Since exceptions must be expected at runtime there are also mechanisms for (selectively) handling them.
(Control.Exception,try
, Control.Exception.catch
)
Unfortunately Haskell's standard library names common exceptions of IO actions IOError
and the module Control.Monad.Error
is about exception handling not error handling.
In general you should be very careful, not to mix up exceptions with errors.
Actually, an unhandled exception is an error.
Implementation
The great thing about Haskell is, that it is not necessary to hard-wire the exception handling into the language.
Everything is already there to implement definition and handling of exceptions nicely.
See the implementation in Control.Monad.Error
(and please, excuse the misleading name, for now).
There is an old dispute between C++ programmers on whether exceptions or error return codes are the right way. Also Niklaus Wirth considered exceptions to be the reincarnation of GOTO and thus omitted them in his languages. Now Haskell solves the problem the diplomatic way: Function return error codes, but the handling of error codes does not uglify the calling code.
First we implement exception handling for non-monadic functions. Since no IO functions are involved, we can still not handle exceptional situations induced from outside the world, but we can handle situations, where it is unacceptable for the caller to check a priori whether the call can succeed.
data ExAction e a =
Success a
| Exception e
deriving (Show)
instance Monad (ExAction e) where
return = Success
Exception l >>= _ = Exception l
Success r >>= k = k r
throw :: e -> ExAction e a
throw = Exception
catch :: ExAction e a -> (e -> ExAction e a) -> ExAction e a
catch (Exception l) h = h l
catch (Success r) _ = Success r
Now we extend this to monadic functions.
This is not restricted to IO, but may be used immediately also for non-deterministic algorithms implemented with the List
monad.
newtype ExActionT e m a =
ExActionT {runExActionT :: m (ExAction e a)}
instance Monad m => Monad (ExActionT e m) where
return = ExActionT . return . Success
m >>= k = ExActionT $
runExActionT m >>= \ a ->
case a of
Exception e -> return (Exception e)
Success r -> runExActionT (k r)
throwT :: Monad m => e -> ExActionT e m a
throwT = ExActionT . return . Exception
catchT :: Monad m =>
ExActionT e m a -> (e -> ExActionT e m a) -> ExActionT e m a
catchT m h = ExActionT $
runExActionT m >>= \ a ->
case a of
Exception l -> runExActionT (h l)
Success r -> return (Success r)
bracketT :: Monad m =>
ExActionT e m h ->
(h -> ExActionT e m ()) ->
(h -> ExActionT e m a) ->
ExActionT e m a
bracketT open close body =
open >>= (\ h ->
ExActionT $
do a <- runExActionT (body h)
runExActionT (close h)
return a)
Here are some examples for typical IO functions with explicit exceptions.
data IOException =
DiskFull
| FileDoesNotExist
| ReadProtected
| WriteProtected
| NoSpaceOnDevice
deriving (Show, Eq, Enum)
open :: FilePath -> ExActionT IOException IO Handle
close :: Handle -> ExActionT IOException IO ()
read :: Handle -> ExActionT IOException IO String
write :: Handle -> String -> ExActionT IOException IO ()
readText :: FilePath -> ExActionT IOException IO String
readText fileName =
bracketT (open fileName) close $ \h ->
read h
Finally we can escape from the Exception monad if we handle the exceptions completely.
main :: IO ()
main =
do result <- runExActionT (readText "test")
case result of
Exception e -> putStrLn ("When reading file 'test' we encountered exception " ++ show e)
Success x -> putStrLn ("Content of the file 'test'\n" ++ x)