IOError vs. Exception vs. IOException

Ross Paterson
Fri, 1 Nov 2002 11:57:34 +0000

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

seems to predate the catch split.  The reason you gave in

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