concrete functor and monad transformers
#85New (0.6.0) qualified superclass constraint on `MonadTrans` breaks Agda
transformers-0.6 came with an extra constraint on MonadTrans
:
class (forall m. Monad m => Monad (t m)) => MonadTrans t where
This breaks compilation of Agda, see: https://github.com/agda/agda/issues/5659#issuecomment-1027367167 In our case t = TCMT
is only a monad if m
is a MonadIO
. This is because the fail
method (which still exists in some GHC versions we support---we support GHC 8.0 - 9.2) is implemented using IO
.
To help me understand what this new constraint's purpose is: Why can some t
satisfying only (forall m. MonadIO m => Monad (t m))
not be a monad transformer? It can still provide access to the monad m
, can't it? Why is it a hinderance that m
has additional services, like IO access?
This regression has been introduced by the "fix" of (non-)issue https://hub.darcs.net/ross/transformers/issue/71.
Here is a MWE:
{-# LANGUAGE CPP #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} module CrashOnFail where import Control.Monad.IO.Class import Control.Monad.Trans.Class import Control.Monad.Fail (MonadFail) import qualified Control.Monad.Fail as Fail import System.Exit (die) -- A monad transformer that implements 'fail' as 'die'. newtype CrashOnFailT m a = CrashOnFailT (m a) deriving (Functor, Applicative) instance MonadIO m => MonadFail (CrashOnFailT m) where fail = CrashOnFailT . liftIO . die instance MonadIO m => Monad (CrashOnFailT m) where CrashOnFailT ma >>= k = CrashOnFailT $ do a <- ma let CrashOnFailT mb = k a mb return = pure #if __GLASGOW_HASKELL__ < 808 fail = Fail.fail #endif -- This breaks with transformers-0.6 starting with GHC 8.6: instance MonadTrans CrashOnFailT where lift = CrashOnFailT
Cabal file for completeness:
cabal-version: >= 1.10 name: MonadTrans-fail version: 0.1.0.0 build-type: Simple library hs-source-dirs: . default-language: Haskell2010 exposed-modules: CrashOnFail build-depends: base , transformers == 0.6.*
I think you said somewhere (that I can't find now) that limiting the constraint to GHC >= 8.8 (instead of 8.6) would fix things for you. Is that still true? (In the above example the Monad (CrashOnFailT m) instance could be defined to only require (Monad m) for GHC >= 8.8.)
So the suggestion for
Agda.TypeChecking.Monad.Base
isreturnTCMT :: Monad m => a -> TCMT m a returnTCMT = \x -> TCM $ \_ _ -> return x {-# INLINE returnTCMT #-} bindTCMT :: Monad m => TCMT m a -> (a -> TCMT m b) -> TCMT m b bindTCMT = \(TCM m) k -> TCM $ \r e -> m r e >>= \x -> unTCM (k x) r e {-# INLINE bindTCMT #-} thenTCMT :: Monad m => TCMT m a -> TCMT m b -> TCMT m b thenTCMT = \(TCM m1) (TCM m2) -> TCM $ \r e -> m1 r e >> m2 r e {-# INLINE thenTCMT #-} instance Monad m => Functor (TCMT m) where fmap = fmapTCMT fmapTCMT :: Monad m => (a -> b) -> TCMT m a -> TCMT m b fmapTCMT = \f (TCM m) -> TCM $ \r e -> fmap f (m r e) {-# INLINE fmapTCMT #-} #if __GLASGOW_HASKELL__ < 808 instance MonadIO m => Applicative (TCMT m) where #else instance Monad m => Applicative (TCMT m) where #endif pure = returnTCMT (<*>) = apTCMT #if __GLASGOW_HASKELL__ < 808 apTCMT :: MonadIO m => TCMT m (a -> b) -> TCMT m a -> TCMT m b #else apTCMT :: Monad m => TCMT m (a -> b) -> TCMT m a -> TCMT m b #endif apTCMT = \(TCM mf) (TCM m) -> TCM $ \r e -> ap (mf r e) (m r e) {-# INLINE apTCMT #-} instance MonadTrans TCMT where lift m = TCM $ \_ _ -> m -- We want a special monad implementation of fail. #if __GLASGOW_HASKELL__ < 808 instance MonadIO m => Monad (TCMT m) where #else instance Monad m => Monad (TCMT m) where #endif return = pure (>>=) = bindTCMT (>>) = (*>) #if __GLASGOW_HASKELL__ < 808 fail = Fail.fail #endif instance MonadIO m => Fail.MonadFail (TCMT m) where fail = internalError
The constraint is now limited to GHC >= 8.8. It seems unreasonable to require a polymorphic instance of
Monad
for earlier versions, when the class included thefail
method.- status set to closed
It seems this fixes the Agda issue.
@phadej remarks in https://github.com/agda/agda/issues/5659#issuecomment-1325301101 that GHC 8.6 has
MonadFailDesugaring
, so the problem could have been solved without changing anything in transformer-0.6:{-# LANGUAGE CPP #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} module CrashOnFail where import Control.Monad.IO.Class import Control.Monad.Trans.Class import Control.Monad.Fail (MonadFail) import qualified Control.Monad.Fail as Fail import System.Exit (die) -- A monad transformer that implements 'fail' as 'die'. newtype CrashOnFailT m a = CrashOnFailT (m a) deriving (Functor, Applicative) instance MonadIO m => MonadFail (CrashOnFailT m) where fail = CrashOnFailT . liftIO . die #if __GLASGOW_HASKELL__ < 806 instance MonadIO m => Monad (CrashOnFailT m) where #else instance Monad m => Monad (CrashOnFailT m) where #endif CrashOnFailT ma >>= k = CrashOnFailT $ do a <- ma let CrashOnFailT mb = k a mb return = pure #if __GLASGOW_HASKELL__ < 806 fail = Fail.fail #endif instance MonadTrans CrashOnFailT where lift = CrashOnFailT
This works with "all" GHCs and
transformers-0.6.0.0/1/2
.