Search code examples
haskellmonadsmonad-transformers

Implementing MonadError through ExceptT


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?


Solution

  • 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