Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the idea behind Haskell libraries throwing exceptions

Tags:

haskell

Why would a library (wreq on a 404 for example) throw an exception instead of wrapping the result into something like Maybe?

Naively, I think Maybe would be better (compiler warning me if I'm not handling all the cases for example). Why am I wrong here?

like image 878
zoran119 Avatar asked Oct 25 '18 10:10

zoran119


People also ask

Should a library throw an exception?

There are many opinion based answers, but there is one answer which absolutely everyone should agree upon: If your library throws an exception, the user must catch it to handle it. Thus, the user's opinion about how you should throw exceptions is the right one.

Does Haskell have exceptions?

There are three different kinds of exceptions in Haskell: one is imprecise; two are precise, of which one is synchronous, and one asynchronous. These were all identified in foundational Haskell papers 1.

Does Haskell have exception handling?

The Haskell runtime system allows any IO action to throw runtime exceptions. Many common library functions throw runtime exceptions. There is no indication at the type level if something throws an exception. You should assume that, unless explicitly documented otherwise, all actions may throw an exception.


1 Answers

Haskellers do strongly strive to avoid throwing exceptions from functions – a function should only throw an exception in truely exceptional cases, i.e. in “this should never happen” situations like the user passing some input that's explicitly forbidden by the documentation. If pure functions regularly threw exceptions, this would be a big problem not only because the type doesn't say what one should be prepared to catch, also one can't catch exceptions within a pure function but only in IO code that calls the function. And even if you can in principle catch the exception, it may be hard to predict where it needs to be done because lazy evaluation can delay the point where it actually happens.

As a matter of fact, even in case of e.g. Wreq.get, no exception is thrown from the function.

Prelude Network.Wreq> get "htt:/p/this-isn't even valid URL syntax" `seq` "Ok"
"Ok"

It is the IO action that throws, when you execute it:

Prelude Network.Wreq> get "htt:/p/this-isn't even valid URL syntax" >> pure ()
*** Exception: InvalidUrlException "htt:/p/this-isn't%20even%20valid%20URL%20syntax" "Invalid scheme"

Now with an IO action, the situation is a bit different. Lots of IO actions can have potentially very different errors in different situations that may be hard or impossible to predict, like a hard-drive crash. Catalogising all the possible errors in a suitable data type for each action would be a major undertaking, and it would be really quite cumbersome to handle every possible case or figure out which parts just to pass on. And simply wrapping the result of every single IO action in Maybe would just lead to a similar situation as in Java where every reference can possibly null. This doesn't tell you anything, and people often wouldn't come up with sensible ways of handling this either.

This is pretty much the problem why exceptions were invented in the first place, and it holds just as well for procedural languages as it holds for Haskell (or rather, it's procedural eDSL that is IO). And because unlike pure functions, IO does have a well-defined time-sequence in its control flow, it's also pretty clear where you must do it if you need to catch some particular exception.

That's not to say it never makes sense for an IO action to return a Maybe / Either value that makes the possible errors explicit, just this isn't always worthwhile.

like image 150
leftaroundabout Avatar answered Nov 04 '22 05:11

leftaroundabout