Use `GHC.IO.catchException` in favor of `GHC.IO.catch`
The former is strict in the IO action, the latter is not, see #11555 (closed). But we almost never need that extra bit of laziness and it indeed hurts performance in a couple of cases. See also the documentation surrounding catchException
:
-- | Catch an exception in the 'IO' monad.
--
-- Note that this function is /strict/ in the action. That is,
-- @catchException undefined b == _|_@. See #exceptions_and_strictness#
-- for details.
catchException :: Exception e => IO a -> (e -> IO a) -> IO a
catchException !io handler = catch io handler
...
{- $exceptions_and_strictness
Laziness can interact with @catch@-like operations in non-obvious ways (see,
e.g. GHC #11555 and #13330). For instance, consider these subtly-different
examples:
> test1 = Control.Exception.catch (error "uh oh") (\(_ :: SomeException) -> putStrLn "it failed")
>
> test2 = GHC.IO.catchException (error "uh oh") (\(_ :: SomeException) -> putStrLn "it failed")
While @test1@ will print "it failed", @test2@ will print "uh oh".
When using 'catchException', exceptions thrown while evaluating the
action-to-be-executed will not be caught; only exceptions thrown during
execution of the action will be handled by the exception handler.
Since this strictness is a small optimization and may lead to surprising
results, all of the @catch@ and @handle@ variants offered by "Control.Exception"
use 'catch' rather than 'catchException'.
-}
So we should try to use catchException
wherever possible (like a1c4230e did), e.g. when it is obvious that construction of the IO
action can't diverge.
Just grepping for Exception.catch
reveals quite some occurrences that probably could use catchException
instead. Possibly viable for a newcomer/someone who just wants to work on a short and simple task.