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