Why would a library (
wreq on a
404 for example) throw an exception instead of wrapping the result into something like
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?
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
Either value that makes the possible errors explicit, just this isn't always worthwhile.