Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When to use 'ioError . userError' instead of 'error'

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?

like image 745
Danny Navarro Avatar asked Feb 13 '23 12:02

Danny Navarro


1 Answers

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.

IOExceptions 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".

like image 99
Nikita Volkov Avatar answered Feb 27 '23 03:02

Nikita Volkov