This post briefly explains why I commonly suggest that people replace error
with fail
when raising IOException
s.
The main difference between error
and fail
can be summarized by the following equations:
In other words, any attempt to evaluate an expression that is an error
will raise the error. Evaluating an expression that is a fail
does not raise the error or trigger any side effects.
Why does this matter? One of the nice properties of Haskell is that Haskell separates effect order from evaluation order. For example, evaluating a print
statement is not the same thing as running it:
{-# LANGUAGE BangPatterns #-}
-- This program only prints "2"
main :: IO ()
main = do
let !x = print 1
print 2
This insensitivity to evaluation order makes Haskell code easier to maintain. Specifically, this insensitivity frees us from concerning ourselves with evaluation order in the same way garbage collection frees us from concerning ourselves with memory management.
Once we begin using evaluation-sensitive primitives such as error
we necessarily need to program with greater caution than before. Now any time we manipulate a subroutine of type IO a
we need to take care not to prematurely force the thunk storing that subroutine.
How likely are we to prematurely evaluate a subroutine? Truthfully, not very likely, but fortunately taking the extra precaution to use fail
is not only theoretically safer, it is also one character shorter than using error
.
Limitations
Note that this advice applies solely to the case of raising IOException
s within an IO
subroutine. fail
is not necessarily safer than error
in other cases, because fail
is a method of the MonadFail
typeclass and the typeclass does not guarantee in general that fail
is safe.
fail
happens to do the correct thing for IO
:
… but for other MonadFail
instances fail
could be a synonym for error
and offer no additional protective value.
If you want to future-proof your code and ensure that you never use the wrong MonadFail
instance, you can do one of two things:
- Enable the
TypeApplications
language extension and writefail @IO string
- Use
Control.Exception.throwIO (userError string)
instead offail
However, even if you choose not to future-proof your code fail
is still no worse than error
in this regard.
No comments:
Post a Comment