RE: IOError vs. Exception vs. IOException

Shouldn't IOError be identified with IOException rather
Ross Paterson
cryptically writes: than Exception? I had to grovel through the code to understand what this question means.
Well, you could have grovelled through the documentation instead :-) http://www.haskell.org/ghc/docs/latest/html/base/Control.Exception.html
It seems that GHC.IOBase contains these definitions:
type IOError = Exception
data Exception = ArithException ArithException | ... | IOException IOException | ...
data IOException = IOError { ioe_handle :: Maybe Handle, -- the handle used by the action flagging -- the error. ioe_type :: IOErrorType, -- what it was. ioe_location :: String, -- location. ioe_descr :: String, -- error type specific information. ioe_filename :: Maybe FilePath -- filename the error is related to. }
I think the idea is that we want code protected using catch clauses to be safe against not just IOErrors but also all the exceptions (division by zero, etc) that can happen. Since the type of 'catch' is fixed by the report, we have to make IOError include all those types.
It didn't always used to be this way: before GHC 5.00, IOError was what is now called IOException. We changed it so that IOError == Exception because it seems simpler this way: IO.ioError can be used to throw exceptions, and Exception.catch and IO.catch have the same type. I think there were more good reasons, but I can't remember now (the change came about when Simon P.J. was trying to describe this stuff for his "awkward squad" paper). Personally I'm not completely happy with the design, the IOError==Exception thing is a bit strange. But most of the complication arises if you try to mix the two interfaces to exceptions (IO and Exception) - if you stick to the Exception interface then the design is quite consistent.
The report deliberately specifies IOError in such a way that additional kinds of IOError can be easily added.
This only leaves the question of whether division by 0 is an IO error or is a pure error. The argument is that the only thing which causes evaluation to happen is IO - if your program doesn't interact with the outside world, it might as well not do anything. So, in that sense, all errors are errors triggered by performing IO. [You can disagree with this argument if you like - the fact will remain that we'd like Prelude.catch to catch as many exceptions as possible even if the names of the types seem a little screwy when we do that.]
You can't have Prelude.catch catch division by zero and pattern match failures (for example), because that wouldn't be Haskell 98. That's why we have Exception.catch which does catch these errors. Cheers, Simon

On Fri, Nov 01, 2002 at 10:38:14AM -0000, Simon Marlow wrote:
It didn't always used to be this way: before GHC 5.00, IOError was what is now called IOException. We changed it so that IOError == Exception because it seems simpler this way: IO.ioError can be used to throw exceptions, and Exception.catch and IO.catch have the same type. I think there were more good reasons, but I can't remember now (the change came about when Simon P.J. was trying to describe this stuff for his "awkward squad" paper).
The reasoning in the original decision http://www.mail-archive.com/glasgow-haskell-users@haskell.org/msg01499.html seems to predate the catch split. The reason you gave in http://www.haskell.org/pipermail/cvs-hugs/2001-April/000490.html was that this eases porting of old code.
Personally I'm not completely happy with the design, the IOError==Exception thing is a bit strange. But most of the complication arises if you try to mix the two interfaces to exceptions (IO and Exception) - if you stick to the Exception interface then the design is quite consistent.
Well that's true in the sense that Exception and IOException come from the Control.Exception interface and IOError comes from the Haskell 98 Prelude+IO interface, and it's only when you use them together that you ask what IOError is identified with. But even without the H98 stuff, ioErrors :: Exception -> Maybe IOError should really be ioErrors :: Exception -> Maybe IOException and it's wierd that the function to throw general exceptions in the IO monad is called ioError. When you bring in the H98 stuff, the abuse of the types is clear. In the Prelude, we have ioError :: IOError -> IO a userError :: String -> IOError catch :: IO a -> (IOError -> IO a) -> IO a but userError produces only IOExceptions, and Prelude.catch catches only IOExceptions. (Having the same type as Control.Exception.catch is a bug, not a feature.) The only gain from identifying IOError = Exception is that you can generalize ioError to all exceptions, despite its name. With IOError = IOException, you would have to add to Control.Exception throwIO :: Exception -> IO a as suggested by Alastair a while ago. In IO (and System.IO.Error) we have isAlreadyExistsError :: IOError -> Bool ... ioeGetErrorString :: IOError -> String ... With IOError = Exception, these functions give runtime errors on anything that isn't actually an IOException. There is also (in IO and System.IO) try :: IO a -> IO (Either IOError a) bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c bracket_ :: IO a -> (a -> IO b) -> IO c -> IO c which again handle only IOExceptions, so there are new versions of these three in Control.Exception. It seems that the old bracket functions should now never be used, unless you know the whole program will be H98. I would advocate moving them to haskell98/IO.hs, so users of the new libraries don't have to hide them. I'm not so sure about moving IO.try too, but it's recoverable as tryJust ioErrors. (This is independent of what IOError means.)
participants (2)
-
Ross Paterson
-
Simon Marlow