Open shlevy opened 1 year ago
Currently putting this into my project
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE StandaloneKindSignatures #-}
module Control.Monad.Trans.Control.Extra where
import Control.Monad.Trans.Class
import Control.Monad.Trans.Control
import Data.Kind
class (MonadTrans t) => MonadTransControlExtra t where
type StTExtra t (m :: Type -> Type) a :: Type
liftWithExtra :: (Monad m) => (RunExtra t m -> m a) -> t m a
restoreTExtra :: (Monad m) => m (StTExtra t m a) -> t m a
type RunExtra t m = forall b. t m b -> m (StTExtra t m b)
-- | DerivingVia helper from the stronger 'MonadTransControl'
type ExtraViaOriginal :: ((Type -> Type) -> (Type -> Type)) -> ((Type -> Type) -> (Type -> Type))
newtype ExtraViaOriginal t m a = ExtraViaOriginal (t m a) deriving newtype (MonadTrans, Monad, Functor, Applicative, MonadTransControl)
instance (MonadTransControl t) => MonadTransControlExtra (ExtraViaOriginal t) where
type StTExtra (ExtraViaOriginal t) m a = StT t a
liftWithExtra = liftWith
restoreTExtra = restoreT
{- snip generating instances for transformers types -}
I have a transformer that is isomorphic to a ReaderT r m except r is an expression referencing m as a parameter
Why?
My first reaction is that you are trying to do something too fancy, and monad-control
is already fancy enough.
So most likely the answer is no, if there isn't a very compelling use case.
For what it's worth, this makes MonadTransControl
less general and arguably less "fancy".
But fair enough on my use case, it's somewhat esoteric, I want to use ReaderT
to implicitly thread through a record of monadic effects which may change within a given scope (like local
)... So the effects need to be in the same monad as the underlying computation.
FWIW, e.g. https://hackage.haskell.org/package/effectful-core-2.2.1.0/docs/Effectful-Internal-Monad.html#t:Eff has MonadBaseControl
instances as then there isn't such problems. (Eff es a
is Env es -> IO a
- read like).
So Eff es
is ReaderT (Env es) IO
. Env es
doesn't need to mention "m" as the IO
is implicitly there. In other words, when working over a concrete base monad things are simpler.
I'd suggest you to look at effectful
. At least for a design perspective.
(using "state" and "context" in the sense used in the MonadUnliftIO documentation)
Currently, because
Run
is polymorphic over its monad, the transformer's context cannot depend on the monad we've transformed. For example, I have a transformer that is isomorphic to aReaderT r m
exceptr
is an expression referencingm
as a parameter, so it can't beMonadTransControl
. If insteadRun
took anm
parameter (or took a functor, not a transformer) andliftWith
was(Run t m -> m a) -> t m a
(or(Run (t m) m -> m a) -> t m a
), the instance could be written; would this limit the class meaningfully?A similar consideration applies to
StT
if the state depends onm
, but I don't currently have a use case requiring it.