This post briefly explains why I commonly suggest that people replace
fail when raising
The main difference between
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
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
Note that this advice applies solely to the case of raising
IOExceptions within an
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
… but for other
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
TypeApplicationslanguage extension and write
fail @IO string
Control.Exception.throwIO (userError string)instead of
However, even if you choose not to future-proof your code
fail is still no worse than
error in this regard.