Can one reasonably implement MonadError
threw a newtype wrapping ExceptT
? So far I have:
newtype BlahT e m a = BlahT (ExceptT e m a) deriving newtype (Functor, Applicative, Monad)
instance MonadError e m => MonadError e (BlahT e' m) where
throwError = BlahT . ExceptT . (Right <$>) . throwError
catchError = _ -- ???
I know catchError = const
will compile, but that's not what I want to do. I basically (think I) want catchError
in the case of ExceptT
being a "Right
" to behave like catchError
would on the underlying monad, but in the case of "Left
" catchError
does nothing (we'll check have to check the return of ExceptT
in this case).
I've bashed my head against this but have got stuck wrestling with the type checker. Is there actually a sensible implementation of catchError
here or is what I'm trying to do impossible for some reason?
CodePudding user response:
Look at the type of catch
:
catch :: m a -> (e -> m a) -> m a
And specialized to BlahT e m
:
catch :: BlahT e m a -> (e -> BlahT e m a) -> BlahT e m a
unfold BlahT e m a = m (Either e a)
catch :: m (Either e a) -> (e -> m (Either e a)) -> m (Either e a)
which is merely a specialization of catch
of the underlying monad m
.
So you can implement BlahT
's catch
as coerce
of m
's catch
.
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, DerivingVia, GeneralizedNewtypeDeriving, UndecidableInstances, ScopedTypeVariables #-}
module E where
import Control.Monad.Except
import Data.Coerce
newtype BlahT e m a = BlahT (ExceptT e m a) deriving newtype (Functor, Applicative, Monad)
type Catch e m a = m a -> (e -> m a) -> m a
instance MonadError e m => MonadError e (BlahT e' m) where
throwError = BlahT . ExceptT . (Right <$>) . throwError
catchError = (coerce :: Catch e m (Either e' a) -> Catch e (BlahT e' m) a) catchError