{-# LANGUAGE LambdaCase #-} module Control.Monad.Trans.Bifunctor ( BifunctorTrans(..) , bimapX , firstX , secondX ) where import Control.Monad.Morph (MMonad, squash, hoist) import Control.Monad.Trans.Except (ExceptT(..), runExceptT) import qualified Control.Monad.Trans.Writer.Lazy as Lazy import qualified Control.Monad.Trans.Writer.Strict as Strict -- | Class of monad transformers which are bifunctors. -- -- You can implement a 'BifunctorTrans' by either defining 'bimapT' or by -- defining both 'firstT' and 'secondT'. -- -- If you supply 'bimapT', you should ensure that: -- -- @'bimapT' 'id' 'id' ≡ 'id'@ -- -- If you supply 'first' and 'second', ensure: -- -- @ -- 'firstT' 'id' ≡ 'id' -- 'secondT' 'id' ≡ 'id' -- @ -- -- If you supply both, you should also ensure: -- -- @'bimapT' f g ≡ 'firstT' f '.' 'secondT' g@ -- -- These ensure by parametricity: -- -- @ -- 'bimapT' (f '.' g) (h '.' i) ≡ 'bimapT' f h '.' 'bimapT' g i -- 'firstT' (f '.' g) ≡ 'firstT' f '.' 'firstT' g -- 'secondT' (f '.' g) ≡ 'secondT' f '.' 'secondT' g -- @ -- class BifunctorTrans t where {-# MINIMAL bimapT | firstT, secondT #-} -- | Map over both arguments at the same time. -- -- @'bimap' f g ≡ 'first' f '.' 'second' g@ -- bimapT :: Functor f => (x -> y) -> (a -> b) -> t x f a -> t y f b bimapT f g = firstT f . secondT g {-# INLINE bimapT #-} -- | Map covariantly over the first argument. -- -- @'firstT' f ≡ 'bimapT' f 'id'@ -- firstT :: Functor f => (x -> y) -> t x f a -> t y f a firstT f = bimapT f id {-# INLINE firstT #-} -- | Map covariantly over the second argument. -- -- @'second' ≡ 'bimap' 'id'@ -- secondT :: Functor f => (a -> b) -> t x f a -> t x f b secondT = bimapT id {-# INLINE secondT #-} instance BifunctorTrans ExceptT where bimapT f g = let h = \case Left x -> Left (f x) Right a -> Right (g a) {-# INLINE h #-} in ExceptT . fmap h . runExceptT {-# INLINE bimapT #-} instance BifunctorTrans Lazy.WriterT where bimapT f g = let h (a, x) = (g a, f x) {-# INLINE h #-} in Lazy.WriterT . fmap h . Lazy.runWriterT {-# INLINE bimapT #-} instance BifunctorTrans Strict.WriterT where bimapT f g = let h (a, x) = (g a, f x) {-# INLINE h #-} in Strict.WriterT . fmap h . Strict.runWriterT {-# INLINE bimapT #-} -- | Map over two stacked transformer bifunctors, unifying their type and -- squashing the result. -- bimapX :: (BifunctorTrans t, MMonad (t z), Monad (t y m), Monad m) => (x -> z) -> (y -> z) -> t x (t y m) a -> t z m a bimapX f g = squash . hoist (firstT g) . firstT f {-# INLINE bimapX #-} -- | Map over the first of two stacked transformer bifunctors, unifying their -- type and squashing the result. -- firstX :: (BifunctorTrans t, MMonad (t y), Functor (t y m), Monad m) => (x -> y) -> t x (t y m) a -> t y m a firstX f = squash . firstT f {-# INLINE firstX #-} -- | Map over the second of two stacked transformer bifunctors, unifying their -- type and squashing the result. -- secondX :: (BifunctorTrans t, MMonad (t x), Monad (t y m), Monad m) => (y -> x) -> t x (t y m) a -> t x m a secondX f = squash . hoist (firstT f) {-# INLINE secondX #-}