A Weird Imagination

Eliminating Control.Monad.Error

The problem#

Compiling the Haskell package language-python (a dependency of xcffib), I got the following warning stating that the typeclass Error is deprecated:

language-python/src/Language/Python/Common/ParseError.hs:25:10: warning: [-Wdeprecations]
    In the use of type constructor or class Error
    (imported from Control.Monad.Error.Class, but defined in Control.Monad.Trans.Error):
    Deprecated: "Use Control.Monad.Trans.Except instead"
   |                                 
25 | instance Error ParseError where 
   |          ^^^^^                  

I wasn't sure how to "Use Control.Monad.Trans.Except instead", as Except is not a drop-in replacement for Error.

The solution#

As this StackOverflow answer recommended,

Short answer is: Replace Error by nothing at all

The code used throwError, which I replaced with

throwError = lift . Left

Other than that, I just removed the imports of Control.Monad.Error and the typeclass instance of Error. The full diff is in this pull request.

The details#

Error handling in Haskell#

This article does a good job of introducing Haskell's approach to error handling. The basic concept is that computations that could have an error are wrapped in an Either where Left values represent errors and Right values represent normal computations. (This, of course isn't the only way to do error handling in Haskell, but it's the most basic.)

The Error type#

The Error typeclass was a way to specialize that Left type to make it explicitly an error type and provide special constructors for errors. As the StackOverflow answer mentioned above explains

It was found that the ability to use any error/exception type at all, polymorphically, was more useful than the somewhat hacky ability to customize conversion of string error messages.

Which is why simply getting rid of the references to Error gets us most of the way there.

Replacing throwError#

language-python referenced one function from Control.Monad.Error: throwError. Effectively, it injected the error as a Left value as expected from the description above.

If you're using mtl, it already defines its own throwError properly. If not, and you're using Either as your error monad, then you'll have to explicitly define it as Left (which is what mtl does).

In the case of language-python, the Either was wrapped inside a StateT, so the Left had to be lifted to apply to the value inside the StateT.

Comments

Have something to add? Post a comment by sending an email to comments@aweirdimagination.net. You may use Markdown for formatting.

There are no comments yet.