To summarise the part of this post that made the biggest impression on me, it shows how a function f can be written in such a way that it can report errors in case of failure, but leaves the choice of how to handle those errors entirely up to the caller. If f is called within a catch expression, it will throw an exception if it fails. If you're expecting an error message back, it'll return one. If it's called in the Maybe monad, it'll return Nothing. You can even implement your own custom error handling strategy in your code (e.g. collect errors and keep going), and f will play along, without having to change its source code.
This only works because of Haskell's expressive type system and pervasive type inference. (Pervasive as opposed to, say, Scala, which infers some types but requires explicit annotation for others.)
Seems to me these are utterly practical use cases, and they constitute real ammunition for the static typing lobby.
Many Monad instances don't override the default implementation of fail that we show here, so in those monads, fail uses error. Calling error is usually highly undesirable, since it throws an exception that callers either cannot catch or will not expect.
Even if you know that right now you're executing in a monad that has fail do something more sensible, we still recommend avoiding it. It's far too easy to cause yourself a problem later when you refactor your code and forget that a previously safe use of fail might be dangerous in its new context.
To summarise the part of this post that made the biggest impression on me, it shows how a function f can be written in such a way that it can report errors in case of failure, but leaves the choice of how to handle those errors entirely up to the caller. If f is called within a catch expression, it will throw an exception if it fails. If you're expecting an error message back, it'll return one. If it's called in the Maybe monad, it'll return Nothing. You can even implement your own custom error handling strategy in your code (e.g. collect errors and keep going), and f will play along, without having to change its source code.
This only works because of Haskell's expressive type system and pervasive type inference. (Pervasive as opposed to, say, Scala, which infers some types but requires explicit annotation for others.)
Seems to me these are utterly practical use cases, and they constitute real ammunition for the static typing lobby.