While reading the source code of the network
package, I noticed the extensive use of ioError (userError ("Error description"))
to raise errors during IO operations.
Since this is not the first time I've seen this, I was wondering what's the difference in practice of using this pattern instead of the error
function from Prelude
.
I'm aware that ioError . userError
raises an IOException
in the IO
monad, while error
raises a ErrorCall
anywhere, but in the end both seem destined to abort the program showing a simple error message.
In which scenarios ioError . userError
would be advantageous over error
?
It's conventional to use error
only to denote a bug. A bug in the sense that something that the developer expects to never happen has happened. Although error
does indeed just throw an ErrorCall
exception, which as such can be caught the same way as any other exception, it's conventional to never catch it, so that the program crashes informing the end-user about the bug with the message provided as a parameter to error
. Later on the user can post that info on the issue tracker for example.
IOException
s on the other hand are meant to be caught and are just an adaptation of the standard C/Java control flow. The userError
is usually used to specify some general case, when none of the more specific types like AlreadyExists
or ResourceBusy
are appropriate.
It must be mentioned that solutions for both of the problems have evolved.
For bug-reporting there exist TemplateHaskell-based libraries such as loch-th and placeholders, which extend the error
to refer to a specific location in the source code and other niceties, like "todo" dummies, which let the compilation pass, but with a warning.
The exceptions of any kind are generally acknowledged as a hack into the otherwise neat Haskell's type system. The biggest problem is that both you and the compiler have no information on whether some computation, e.g. IO ()
, will raise any kind of exception. And that has proven to be awful, with the emergence of a pattern of an exception thrown by some low-level library deeply nested in the dependencies hierarchy crawling out, which then triggers a chain of bug reports on different projects and inflicts tons of pain. That is why complete replacements for the exception-based control-flow have lately been developing, such as EitherT
monad transformer with a related errors util-library, or the ErrorT
transformer. Both of the solutions make exceptions explicitly represented in the type system. The ErrorT
however has received some criticism in favour of EitherT
+"errors".
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