Home > Software engineering >  Implementing MonadError through ExceptT
Implementing MonadError through ExceptT

Time:01-05

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
  • Related