MTL Release (Part 3)
This morning, I had a bit of time to start making ginger
compatible with the recent MTL
release. Most errors are resolved by changing imports, but I ran
into an error that cannot be fixed so easily. The current version of transformers
removed the long deprecated ErrorT
transformer, so code that makes use of ErrorT
needs to be refactored.
The RegexMaker
type class defined in the regex-base
package has methods for creating regular expressions. The makeRegexOptsM
method is the variant that allows you to specify options as well as
reports errors using MonadFail
.
makeRegexOptsM :: MonadFail m => compOpt -> execOpt -> source -> m regex
The use of MonadFail
is unfortunate. The type of this function is equivalent to the following
type. The function is pure and can return either an error message or a
regular expression.
makeRegexOpts' :: compOpt -> execOpt -> source -> Either String regex
With such a simple type, one can easily use MonadFail
by composing with either fail pure
. It is unfortunately not
as easy to convert from MonadFail
to an Either
type. The makeRegexOptsM
function was previously used with the now deprecated ErrorT
transformer, which has the following MonadFail
instance. With this instance, any use of fail
returns the error as an ErrorT
error.
instance (Monad m, Error e) => Fail.MonadFail (ErrorT e m) where
fail msg = ErrorT $ return (Left (strMsg msg))
Most uses of ErrorT
can be refactored to use ExceptT
instead, but the MonadFail
instance of ExceptT
simply delegates the fail
call to the extended monad, so ExceptT
cannot be used in this case.
instance (Fail.MonadFail m) => Fail.MonadFail (ExceptT e m) where
fail = ExceptT . Fail.fail
Kazuki Okamoto created a
package called either-result
that wraps ExceptT
in a type called ResultT
that just changes the MonadFail
instance to behave like the ErrorT
instance. I could use this to resolve my issue in ginger
,
but I am not keen on adding a dependency for something so simple.
instance Monad m => MonadFail (ResultT m) where
fail = throwE
Since the makeRegexOptsM
function is pure, I do not even need to use a monad transformer. I think
I will just use code like the following. Note that it requires the DerivingStrategies
and GeneralizedNewtypeDeriving
extensions.
newtype FailToEither a
= FailToEither
runFailToEither :: Either String a
{
}deriving newtype (Applicative, Functor, Monad)
instance MonadFail FailToEither where
fail = FailToEither . Left
{-# INLINE fail #-}
This is just a newtype
wrapper around Either
.
It uses the existing Functor
,
Applicative
,
and Monad
instances, and a MonadFail
instance is implemented as Left
.