| Haskell definitely has problems. I'm a huge fan of the language, but I think its disingenuous to pretend like its flawless. To name a few: 1. Poor debugging tools. Unfortunately this is sort of intrinsically tied with non-strict evaluation. Typical evaluation stepping debuggers would be sort of unpredictable in haskell. Along these lines, haskell doesn't have stack traces enabled by default, and reading them is sort of tricky. 2. Clumsy exceptions model. There are asynchronous and synchronous exceptions, with the later being further broken down into exceptions in pure code or exceptions thrown in IO. Only exceptions thrown in IO are catchable. You can think of exceptions in pure code as similar to "panic()" calls in other languages, except they're even tricker due to lazy evaluation, and aren't necessarily guaranteed to be triggered due to slipperiness with laziness. Also since exceptions are used for interrupting computations (timeouts, for instance) and there isn't a way (other than some type class conventions) to distinguish between interrupting exceptions that should be allowed to propagate and exceptions due to "exceptional circumstances", catching all exceptions safely is a tricky matter. 3. Record syntax leaves a lot to be desired. It's incredibly easy to run into situations where you would have conflicting functions due to record syntax. Lenses are a partial fix for this, but its sort of annoying that something like row polymorphism isn't just a part of the language. 4. Laziness can make reasoning about time and space complexity trickier. Generally this is a little overstated, but you'll occasionally run into space leaks. There are definitely real problems with the language, and some of them (such as poor debugging capabilities) can be legitimate showstoppers to use in industry, but generally I find that its advantages far outweigh its negatives. |
So far as I'm aware, there is no technical difference between exceptions thrown in IO versus exceptions thrown in pure code. There are two contextual differences.
First, exceptions can only be caught in IO, but everything running has IO above it somewhere, or it wouldn't be running in the first place.
Regarding exceptions thrown in IO versus elsewhere, it's worth noting that exceptions thrown anywhere are only actually thrown if the thunk representing them is forced. IO values tend to be used quite close to where they are created, whereas an exception in lazy, pure code might hide in the creation of the leaf of a tree or at the end of a list.