Implementation-oriented Monad
I am currently migrating some hand-crafted Monad
looking like this:
And some helpers used a bit everywhere:
I call these implementation-oriented Monad
s, as they restrict implementation
to mostly one implementation:
newtype MyAppM a = MyAppM {runMyAppM :: ReaderT AppContext IO a}
deriving newtype (Functor, Applicative, Monad, MonadFail)
data AppContext = AppContext
{ acSmtpConfig :: SmtpConfig,
}
getSmtpConfig = MyAppM $ asks acSmtpConfig
getS3Config = MyAppM $ asks acS3Config
getRedisParams = MyAppM $ asks acRedisParams
it suffers from the following drawbacks:
- Static behaviors (only backends' parameters can be changed, not behaviors)
- High coupling (every piece of code is coupled to the whole
Monad
, even if they are only using one member, or for one purpose) - Types opacity
The most basic refactoring would be to integrate helpers into the type class
:
We still have the type opacity, but at least, we are free to have a completely different implementation, such as a stub:
newtype NoopAppM a = NoopAppM {runNoopAppM :: IO a}
deriving newtype (Functor, Applicative, Monad, MonadFail)
fetchUser' _ = NoopAppM $ return Nothing
storeAvatar' _ _ = NoopAppM $ return ()
notifySignUp' _ _ = NoopAppM $ return ()