I'm refactoring some old code, which is in a polymorphic, but type-class constrained, monad:
class ( MonadIO m
, MonadLogger m
, MonadLoggerIO m
, MonadThrow m
, MonadCatch m
, MonadMask m
, MonadBaseControl IO m
, MonadUnliftIO) => HasLogging m where
In the older code the application's main monad was...
type AppM = ReaderT Env IO
...which will now change to...
newtype AppM (features :: [FeatureFlag]) a = AppM (ReaderT Env IO a)
deriving (Functor, Applicative, Monad, MonadReader Env, MonadIO)
Given this context, is it safe to derive the following, automatically:
- MonadThrow
- MonadCatch
- MonadMask
- MonadBaseControl
- MonadUliftIO
Without getting into GHC internals, what's the best way to develop intuition about what's actually happening when the compiler derives things automagically?
The user manual has documentation about every extension, and it keeps getting better; here's the section on deriving, that should be sufficient to know what's actually happening: https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#extensions-to-the-deriving-mechanism
In this case, all those classes are handled by GeneralizedNewtypeDeriving
.
{-# LANGUAGE GeneralizedNewtypeDeriving, UndecidableInstances #-}
module M where
import Control.Monad.IO.Unlift
import Control.Monad.Catch
import Control.Monad.Trans.Control
import Control.Monad.Base
import Control.Monad.Reader
newtype Foo a = Foo (ReaderT () IO a)
deriving (Functor, Applicative, Monad, MonadIO, MonadUnliftIO, MonadThrow, MonadCatch, MonadMask, MonadBase IO, MonadBaseControl IO)
In general, the three relevant extensions for user-defined classes are GeneralizedNewtypeDeriving
, DerivingVia
, and DeriveAnyType
. And it's also worth enabling DerivingStrategies
to make it explicit which is being used.