dep-t-advice 0.5.0.0 → 0.5.1.0
raw patch · 18 files changed
+2352/−1846 lines, 18 filesdep +microlensdep ~dep-tPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: microlens
Dependency ranges changed: dep-t
API changes (from Hackage documentation)
- Control.Monad.Dep.Advice: I :: a -> I a
- Control.Monad.Dep.Advice: [:*] :: forall k (a :: k -> Type) (x :: k) (xs :: [k]). a x -> NP a xs -> NP a (x : xs)
- Control.Monad.Dep.Advice: [Dict] :: forall k (c :: k -> Constraint) (a :: k). c a => Dict c a
- Control.Monad.Dep.Advice: [Nil] :: forall k (a :: k -> Type). NP a ('[] :: [k])
- Control.Monad.Dep.Advice: advise :: forall ca e_ m r as advisee. (Multicurryable as e_ m r advisee, All ca as, Monad m) => Advice ca e_ m r -> advisee -> advisee
- Control.Monad.Dep.Advice: adviseRecord :: forall ca cr e_ m advised. AdvisedRecord ca e_ m cr advised => (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca e_ m r) -> advised (DepT e_ m) -> advised (DepT e_ m)
- Control.Monad.Dep.Advice: cfoldMap_NP :: forall k c (xs :: [k]) m proxy f. (All c xs, Monoid m) => proxy c -> (forall (a :: k). c a => f a -> m) -> NP f xs -> m
- Control.Monad.Dep.Advice: class (AllF c xs, SListI xs) => All (c :: k -> Constraint) (xs :: [k])
- Control.Monad.Dep.Advice: class (f x, g x) => And (f :: k -> Constraint) (g :: k -> Constraint) (x :: k)
- Control.Monad.Dep.Advice: class Top (x :: k)
- Control.Monad.Dep.Advice: component :: forall e_ m record. (Applicative m, DistributiveRecord e_ m record) => (e_ (DepT e_ m) -> record (DepT e_ m)) -> record (DepT e_ m)
- Control.Monad.Dep.Advice: data Advice (ca :: Type -> Constraint) (e_ :: (Type -> Type) -> Type) m r
- Control.Monad.Dep.Advice: data Dict (c :: k -> Constraint) (a :: k)
- Control.Monad.Dep.Advice: data NP (a :: k -> Type) (b :: [k])
- Control.Monad.Dep.Advice: deceive :: forall as e e_ m r curried. Gullible as e e_ m r curried => (e_ (DepT e_ m) -> e) -> NewtypedEnv as e e_ m r curried -> curried
- Control.Monad.Dep.Advice: deceiveRecord :: forall e e_ m gullible. GullibleRecord e e_ m gullible => (e_ (DepT e_ m) -> e) -> gullible (ReaderT e m) -> gullible (DepT e_ m)
- Control.Monad.Dep.Advice: fromSimple :: forall ca e_ m r. Monad m => (e_ (DepT e_ m) -> Advice ca (DepT e_ m) r) -> Advice ca e_ m r
- Control.Monad.Dep.Advice: infixl 7 `And`
- Control.Monad.Dep.Advice: infixr 5 :*
- Control.Monad.Dep.Advice: instance (Control.Monad.Dep.Advice.AdvisedComponent (Control.Monad.Dep.Advice.DiscriminateAdvisedComponent advised) ca e_ m cr advised, GHC.TypeLits.KnownSymbol fieldName) => Control.Monad.Dep.Advice.AdvisedProduct ca e_ m cr (GHC.Generics.S1 ('GHC.Generics.MetaSel ('GHC.Maybe.Just fieldName) su ss ds) (GHC.Generics.Rec0 advised))
- Control.Monad.Dep.Advice: instance (Control.Monad.Dep.Advice.Gullible as e e_ m r deceived, Control.Monad.Dep.Advice.NewtypedEnv as e e_ m r deceived GHC.Types.~ gullible) => Control.Monad.Dep.Advice.GullibleComponent 'Control.Monad.Dep.Advice.Terminal e e_ m gullible deceived
- Control.Monad.Dep.Advice: instance (Control.Monad.Dep.Advice.Multicurryable as e_ m r advised, Data.SOP.Constraint.All ca as, cr r, GHC.Base.Monad m) => Control.Monad.Dep.Advice.AdvisedComponent 'Control.Monad.Dep.Advice.Terminal ca e_ m cr advised
- Control.Monad.Dep.Advice: instance (GHC.Base.Functor m, Control.Monad.Dep.Advice.Gullible as e e_ m r curried) => Control.Monad.Dep.Advice.Gullible (a : as) e e_ m r (a -> curried)
- Control.Monad.Dep.Advice: instance (GHC.Base.Functor m, Control.Monad.Dep.Advice.Multicurryable as e_ m r advised) => Control.Monad.Dep.Advice.DistributiveProduct e_ m (GHC.Generics.S1 ('GHC.Generics.MetaSel msymbol su ss ds) (GHC.Generics.Rec0 advised))
- Control.Monad.Dep.Advice: instance (GHC.Base.Functor m, Control.Monad.Dep.Advice.Multicurryable as e_ m r curried) => Control.Monad.Dep.Advice.Multicurryable (a : as) e_ m r (a -> curried)
- Control.Monad.Dep.Advice: instance (GHC.Generics.Generic (advised (Control.Monad.Dep.DepT e_ m)), GHC.Generics.Rep (advised (Control.Monad.Dep.DepT e_ m)) GHC.Types.~ GHC.Generics.D1 x (GHC.Generics.C1 y advised_), Control.Monad.Dep.Advice.DistributiveProduct e_ m advised_, GHC.Base.Functor m) => Control.Monad.Dep.Advice.DistributiveRecord e_ m advised
- Control.Monad.Dep.Advice: instance (GHC.Generics.Generic (advised (Control.Monad.Dep.DepT e_ m)), GHC.Generics.Rep (advised (Control.Monad.Dep.DepT e_ m)) GHC.Types.~ GHC.Generics.D1 x (GHC.Generics.C1 y advised_), Data.Typeable.Internal.Typeable advised, Control.Monad.Dep.Advice.AdvisedProduct ca e_ m cr advised_) => Control.Monad.Dep.Advice.AdvisedRecord ca e_ m cr advised
- Control.Monad.Dep.Advice: instance (GHC.Generics.Generic (gullible (Control.Monad.Trans.Reader.ReaderT e m)), GHC.Generics.Generic (gullible (Control.Monad.Dep.DepT e_ m)), GHC.Generics.Rep (gullible (Control.Monad.Trans.Reader.ReaderT e m)) GHC.Types.~ GHC.Generics.D1 x (GHC.Generics.C1 y gullible_), GHC.Generics.Rep (gullible (Control.Monad.Dep.DepT e_ m)) GHC.Types.~ GHC.Generics.D1 x (GHC.Generics.C1 y deceived_), Control.Monad.Dep.Advice.GullibleProduct e e_ m gullible_ deceived_) => Control.Monad.Dep.Advice.GullibleRecord e e_ m gullible
- Control.Monad.Dep.Advice: instance Control.Monad.Dep.Advice.AdvisedComponent (Control.Monad.Dep.Advice.DiscriminateAdvisedComponent advised) ca e_ m cr advised => Control.Monad.Dep.Advice.AdvisedComponent 'Control.Monad.Dep.Advice.IWrapped ca e_ m cr (Data.Functor.Identity.Identity advised)
- Control.Monad.Dep.Advice: instance Control.Monad.Dep.Advice.AdvisedComponent (Control.Monad.Dep.Advice.DiscriminateAdvisedComponent advised) ca e_ m cr advised => Control.Monad.Dep.Advice.AdvisedComponent 'Control.Monad.Dep.Advice.IWrapped ca e_ m cr (Data.SOP.BasicFunctors.I advised)
- Control.Monad.Dep.Advice: instance Control.Monad.Dep.Advice.AdvisedRecord ca e_ m cr advisable => Control.Monad.Dep.Advice.AdvisedComponent 'Control.Monad.Dep.Advice.Recurse ca e_ m cr (advisable (Control.Monad.Dep.DepT e_ m))
- Control.Monad.Dep.Advice: instance Control.Monad.Dep.Advice.GullibleComponent (Control.Monad.Dep.Advice.DiscriminateGullibleComponent gullible) e e_ m gullible deceived => Control.Monad.Dep.Advice.GullibleComponent 'Control.Monad.Dep.Advice.IWrapped e e_ m (Data.Functor.Identity.Identity gullible) (Data.Functor.Identity.Identity deceived)
- Control.Monad.Dep.Advice: instance Control.Monad.Dep.Advice.GullibleComponent (Control.Monad.Dep.Advice.DiscriminateGullibleComponent gullible) e e_ m gullible deceived => Control.Monad.Dep.Advice.GullibleComponent 'Control.Monad.Dep.Advice.IWrapped e e_ m (Data.SOP.BasicFunctors.I gullible) (Data.SOP.BasicFunctors.I deceived)
- Control.Monad.Dep.Advice: instance Control.Monad.Dep.Advice.GullibleComponent (Control.Monad.Dep.Advice.DiscriminateGullibleComponent gullible) e e_ m gullible deceived => Control.Monad.Dep.Advice.GullibleProduct e e_ m (GHC.Generics.S1 x (GHC.Generics.Rec0 gullible)) (GHC.Generics.S1 x (GHC.Generics.Rec0 deceived))
- Control.Monad.Dep.Advice: instance Control.Monad.Dep.Advice.GullibleRecord e e_ m gullible => Control.Monad.Dep.Advice.GullibleComponent 'Control.Monad.Dep.Advice.Recurse e e_ m (gullible (Control.Monad.Trans.Reader.ReaderT e m)) (gullible (Control.Monad.Dep.DepT e_ m))
- Control.Monad.Dep.Advice: instance GHC.Base.Monad m => Control.Monad.Dep.Advice.Gullible '[] e e_ m r (Control.Monad.Dep.DepT e_ m r)
- Control.Monad.Dep.Advice: instance GHC.Base.Monad m => Control.Monad.Dep.Advice.Multicurryable '[] e_ m r (Control.Monad.Dep.DepT e_ m r)
- Control.Monad.Dep.Advice: instance GHC.Base.Monad m => GHC.Base.Monoid (Control.Monad.Dep.Advice.Advice ca e_ m r)
- Control.Monad.Dep.Advice: instance GHC.Base.Monad m => GHC.Base.Semigroup (Control.Monad.Dep.Advice.Advice ca e_ m r)
- Control.Monad.Dep.Advice: instance forall k (ca :: * -> GHC.Types.Constraint) (e_ :: (* -> *) -> *) (m :: * -> *) (cr :: * -> GHC.Types.Constraint) (advised_left :: k -> *) (advised_right :: k -> *). (Control.Monad.Dep.Advice.AdvisedProduct ca e_ m cr advised_left, Control.Monad.Dep.Advice.AdvisedProduct ca e_ m cr advised_right) => Control.Monad.Dep.Advice.AdvisedProduct ca e_ m cr (advised_left GHC.Generics.:*: advised_right)
- Control.Monad.Dep.Advice: instance forall k (e_ :: (* -> *) -> *) (m :: * -> *) (advised_left :: k -> *) (advised_right :: k -> *). (Control.Monad.Dep.Advice.DistributiveProduct e_ m advised_left, Control.Monad.Dep.Advice.DistributiveProduct e_ m advised_right, GHC.Base.Functor m) => Control.Monad.Dep.Advice.DistributiveProduct e_ m (advised_left GHC.Generics.:*: advised_right)
- Control.Monad.Dep.Advice: instance forall k e (e_ :: (* -> *) -> *) (m :: * -> *) (gullible_left :: k -> *) (deceived_left :: k -> *) (gullible_right :: k -> *) (deceived_right :: k -> *). (Control.Monad.Dep.Advice.GullibleProduct e e_ m gullible_left deceived_left, Control.Monad.Dep.Advice.GullibleProduct e e_ m gullible_right deceived_right) => Control.Monad.Dep.Advice.GullibleProduct e e_ m (gullible_left GHC.Generics.:*: gullible_right) (deceived_left GHC.Generics.:*: deceived_right)
- Control.Monad.Dep.Advice: makeAdvice :: forall ca e_ m r. (forall as. All ca as => NP I as -> DepT e_ m (DepT e_ m r -> DepT e_ m r, NP I as)) -> Advice ca e_ m r
- Control.Monad.Dep.Advice: makeArgsAdvice :: forall ca e_ m r. Monad m => (forall as. All ca as => NP I as -> DepT e_ m (NP I as)) -> Advice ca e_ m r
- Control.Monad.Dep.Advice: makeExecutionAdvice :: forall ca e_ m r. Applicative m => (DepT e_ m r -> DepT e_ m r) -> Advice ca e_ m r
- Control.Monad.Dep.Advice: newtype I a
- Control.Monad.Dep.Advice: restrictArgs :: forall more less e_ m r. (forall x. Dict more x -> Dict less x) -> Advice less e_ m r -> Advice more e_ m r
- Control.Monad.Dep.Advice: runFinalDepT :: forall as e_ m r curried. Multicurryable as e_ m r curried => m (e_ (DepT e_ m)) -> curried -> DownToBaseMonad as e_ m r curried
- Control.Monad.Dep.Advice: runFromDep :: forall dep as e_ m r curried. (Multicurryable as e_ m r curried, Has dep (DepT e_ m) (e_ (DepT e_ m))) => m (e_ (DepT e_ m)) -> (dep (DepT e_ m) -> curried) -> DownToBaseMonad as e_ m r curried
- Control.Monad.Dep.Advice: runFromEnv :: forall as e_ m r curried. Multicurryable as e_ m r curried => m (e_ (DepT e_ m)) -> (e_ (DepT e_ m) -> curried) -> DownToBaseMonad as e_ m r curried
- Control.Monad.Dep.Advice: toSimple :: Monad m => Advice ca NilEnv m r -> Advice ca m r
- Control.Monad.Dep.Advice: type Ensure c e_ m = c (DepT e_ m) (e_ (DepT e_ m))
- Control.Monad.Dep.Advice.Basic: doLocally :: forall ca e_ m r. Monad m => (e_ (DepT e_ m) -> e_ (DepT e_ m)) -> Advice ca e_ m r
- Control.Monad.Dep.SimpleAdvice: AspectT :: m r -> AspectT (m :: Type -> Type) (r :: Type)
- Control.Monad.Dep.SimpleAdvice: I :: a -> I a
- Control.Monad.Dep.SimpleAdvice: [:*] :: forall k (a :: k -> Type) (x :: k) (xs :: [k]). a x -> NP a xs -> NP a (x : xs)
- Control.Monad.Dep.SimpleAdvice: [Dict] :: forall k (c :: k -> Constraint) (a :: k). c a => Dict c a
- Control.Monad.Dep.SimpleAdvice: [Nil] :: forall k (a :: k -> Type). NP a ('[] :: [k])
- Control.Monad.Dep.SimpleAdvice: [runAspectT] :: AspectT (m :: Type -> Type) (r :: Type) -> m r
- Control.Monad.Dep.SimpleAdvice: advise :: forall ca m r as advisee. (Multicurryable as m r advisee, All ca as, Monad m) => Advice ca m r -> advisee -> advisee
- Control.Monad.Dep.SimpleAdvice: adviseRecord :: forall ca cr m advised. AdvisedRecord ca m cr advised => (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca m r) -> advised (AspectT m) -> advised (AspectT m)
- Control.Monad.Dep.SimpleAdvice: advising :: Coercible (r_ m) (r_ (AspectT m)) => (r_ (AspectT m) -> r_ (AspectT m)) -> r_ m -> r_ m
- Control.Monad.Dep.SimpleAdvice: cfoldMap_NP :: forall k c (xs :: [k]) m proxy f. (All c xs, Monoid m) => proxy c -> (forall (a :: k). c a => f a -> m) -> NP f xs -> m
- Control.Monad.Dep.SimpleAdvice: class (AllF c xs, SListI xs) => All (c :: k -> Constraint) (xs :: [k])
- Control.Monad.Dep.SimpleAdvice: class (f x, g x) => And (f :: k -> Constraint) (g :: k -> Constraint) (x :: k)
- Control.Monad.Dep.SimpleAdvice: class Top (x :: k)
- Control.Monad.Dep.SimpleAdvice: data Advice (ca :: Type -> Constraint) m r
- Control.Monad.Dep.SimpleAdvice: data Dict (c :: k -> Constraint) (a :: k)
- Control.Monad.Dep.SimpleAdvice: data NP (a :: k -> Type) (b :: [k])
- Control.Monad.Dep.SimpleAdvice: infixl 7 `And`
- Control.Monad.Dep.SimpleAdvice: infixr 5 :*
- Control.Monad.Dep.SimpleAdvice: instance (Control.Monad.Dep.SimpleAdvice.AdvisedComponent (Control.Monad.Dep.SimpleAdvice.DiscriminateAdvisedComponent advised) ca m cr advised, GHC.TypeLits.KnownSymbol fieldName) => Control.Monad.Dep.SimpleAdvice.AdvisedProduct ca m cr (GHC.Generics.S1 ('GHC.Generics.MetaSel ('GHC.Maybe.Just fieldName) su ss ds) (GHC.Generics.Rec0 advised))
- Control.Monad.Dep.SimpleAdvice: instance (Control.Monad.Dep.SimpleAdvice.Multicurryable as m r advised, Data.SOP.Constraint.All ca as, cr r, GHC.Base.Monad m) => Control.Monad.Dep.SimpleAdvice.AdvisedComponent 'Control.Monad.Dep.SimpleAdvice.Terminal ca m cr advised
- Control.Monad.Dep.SimpleAdvice: instance (GHC.Generics.Generic (advised (Control.Monad.Dep.SimpleAdvice.Internal.AspectT m)), GHC.Generics.Rep (advised (Control.Monad.Dep.SimpleAdvice.Internal.AspectT m)) GHC.Types.~ GHC.Generics.D1 x (GHC.Generics.C1 y advised_), Data.Typeable.Internal.Typeable advised, Control.Monad.Dep.SimpleAdvice.AdvisedProduct ca m cr advised_) => Control.Monad.Dep.SimpleAdvice.AdvisedRecord ca m cr advised
- Control.Monad.Dep.SimpleAdvice: instance Control.Monad.Dep.SimpleAdvice.AdvisedComponent (Control.Monad.Dep.SimpleAdvice.DiscriminateAdvisedComponent advised) ca m cr advised => Control.Monad.Dep.SimpleAdvice.AdvisedComponent 'Control.Monad.Dep.SimpleAdvice.IWrapped ca m cr (Data.Functor.Identity.Identity advised)
- Control.Monad.Dep.SimpleAdvice: instance Control.Monad.Dep.SimpleAdvice.AdvisedComponent (Control.Monad.Dep.SimpleAdvice.DiscriminateAdvisedComponent advised) ca m cr advised => Control.Monad.Dep.SimpleAdvice.AdvisedComponent 'Control.Monad.Dep.SimpleAdvice.IWrapped ca m cr (Data.SOP.BasicFunctors.I advised)
- Control.Monad.Dep.SimpleAdvice: instance Control.Monad.Dep.SimpleAdvice.AdvisedRecord ca m cr advisable => Control.Monad.Dep.SimpleAdvice.AdvisedComponent 'Control.Monad.Dep.SimpleAdvice.Recurse ca m cr (advisable (Control.Monad.Dep.SimpleAdvice.Internal.AspectT m))
- Control.Monad.Dep.SimpleAdvice: instance Control.Monad.Dep.SimpleAdvice.Multicurryable as m r curried => Control.Monad.Dep.SimpleAdvice.Multicurryable (a : as) m r (a -> curried)
- Control.Monad.Dep.SimpleAdvice: instance GHC.Base.Monad m => Control.Monad.Dep.SimpleAdvice.Multicurryable '[] m r (Control.Monad.Dep.SimpleAdvice.Internal.AspectT m r)
- Control.Monad.Dep.SimpleAdvice: instance forall k (ca :: * -> GHC.Types.Constraint) (m :: * -> *) (cr :: * -> GHC.Types.Constraint) (advised_left :: k -> *) (advised_right :: k -> *). (Control.Monad.Dep.SimpleAdvice.AdvisedProduct ca m cr advised_left, Control.Monad.Dep.SimpleAdvice.AdvisedProduct ca m cr advised_right) => Control.Monad.Dep.SimpleAdvice.AdvisedProduct ca m cr (advised_left GHC.Generics.:*: advised_right)
- Control.Monad.Dep.SimpleAdvice: makeAdvice :: forall ca m r. (forall as. All ca as => NP I as -> AspectT m (AspectT m r -> AspectT m r, NP I as)) -> Advice ca m r
- Control.Monad.Dep.SimpleAdvice: makeArgsAdvice :: forall ca m r. Monad m => (forall as. All ca as => NP I as -> AspectT m (NP I as)) -> Advice ca m r
- Control.Monad.Dep.SimpleAdvice: makeExecutionAdvice :: forall ca m r. Applicative m => (AspectT m r -> AspectT m r) -> Advice ca m r
- Control.Monad.Dep.SimpleAdvice: newtype AspectT (m :: Type -> Type) (r :: Type)
- Control.Monad.Dep.SimpleAdvice: newtype I a
- Control.Monad.Dep.SimpleAdvice: restrictArgs :: forall more less m r. (forall x. Dict more x -> Dict less x) -> Advice less m r -> Advice more m r
- Control.Monad.Dep.SimpleAdvice.Basic: [AnyEq] :: forall a. (Typeable a, Eq a) => a -> AnyEq
- Control.Monad.Dep.SimpleAdvice.Basic: data AnyEq
- Control.Monad.Dep.SimpleAdvice.Basic: doAsyncBadly :: forall ca m. MonadUnliftIO m => Advice ca m ()
- Control.Monad.Dep.SimpleAdvice.Basic: doCachingBadly :: forall m r. Monad m => (AnyEq -> m (Maybe r)) -> (AnyEq -> r -> m ()) -> Advice (Eq `And` Typeable) m r
- Control.Monad.Dep.SimpleAdvice.Basic: instance GHC.Classes.Eq Control.Monad.Dep.SimpleAdvice.Basic.AnyEq
- Control.Monad.Dep.SimpleAdvice.Basic: printArgs :: forall m r. MonadIO m => Handle -> String -> Advice Show m r
- Control.Monad.Dep.SimpleAdvice.Basic: returnMempty :: forall ca m r. (Monad m, Monoid r) => Advice ca m r
+ Dep.Advice: I :: a -> I a
+ Dep.Advice: [:*] :: forall k (a :: k -> Type) (x :: k) (xs :: [k]). a x -> NP a xs -> NP a (x : xs)
+ Dep.Advice: [Dict] :: forall k (c :: k -> Constraint) (a :: k). c a => Dict c a
+ Dep.Advice: [Nil] :: forall k (a :: k -> Type). NP a ('[] :: [k])
+ Dep.Advice: advise :: forall ca e_ m r as advisee. (Multicurryable as e_ m r advisee, All ca as, Monad m) => Advice ca e_ m r -> advisee -> advisee
+ Dep.Advice: adviseRecord :: forall ca cr e_ m advised. AdvisedRecord ca e_ m cr advised => (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca e_ m r) -> advised (DepT e_ m) -> advised (DepT e_ m)
+ Dep.Advice: cfoldMap_NP :: forall k c (xs :: [k]) m proxy f. (All c xs, Monoid m) => proxy c -> (forall (a :: k). c a => f a -> m) -> NP f xs -> m
+ Dep.Advice: class (AllF c xs, SListI xs) => All (c :: k -> Constraint) (xs :: [k])
+ Dep.Advice: class (f x, g x) => And (f :: k -> Constraint) (g :: k -> Constraint) (x :: k)
+ Dep.Advice: class Top (x :: k)
+ Dep.Advice: component :: forall e_ m record. (Applicative m, DistributiveRecord e_ m record) => (e_ (DepT e_ m) -> record (DepT e_ m)) -> record (DepT e_ m)
+ Dep.Advice: data Advice (ca :: Type -> Constraint) (e_ :: (Type -> Type) -> Type) m r
+ Dep.Advice: data Dict (c :: k -> Constraint) (a :: k)
+ Dep.Advice: data NP (a :: k -> Type) (b :: [k])
+ Dep.Advice: deceive :: forall as e e_ m r curried. Gullible as e e_ m r curried => (e_ (DepT e_ m) -> e) -> NewtypedEnv as e e_ m r curried -> curried
+ Dep.Advice: deceiveRecord :: forall e e_ m gullible. GullibleRecord e e_ m gullible => (e_ (DepT e_ m) -> e) -> gullible (ReaderT e m) -> gullible (DepT e_ m)
+ Dep.Advice: fromSimple :: forall ca e_ m r. Monad m => (e_ (DepT e_ m) -> Advice ca (DepT e_ m) r) -> Advice ca e_ m r
+ Dep.Advice: fromSimple_ :: forall ca e_ m r. Monad m => Advice ca (DepT e_ m) r -> Advice ca e_ m r
+ Dep.Advice: infixl 7 `And`
+ Dep.Advice: infixr 5 :*
+ Dep.Advice: instance (Dep.Advice.AdvisedComponent (Dep.Advice.DiscriminateAdvisedComponent advised) ca e_ m cr advised, GHC.TypeLits.KnownSymbol fieldName) => Dep.Advice.AdvisedProduct ca e_ m cr (GHC.Generics.S1 ('GHC.Generics.MetaSel ('GHC.Maybe.Just fieldName) su ss ds) (GHC.Generics.Rec0 advised))
+ Dep.Advice: instance (Dep.Advice.Gullible as e e_ m r deceived, Dep.Advice.NewtypedEnv as e e_ m r deceived GHC.Types.~ gullible) => Dep.Advice.GullibleComponent 'Dep.Advice.Terminal e e_ m gullible deceived
+ Dep.Advice: instance (Dep.Advice.Multicurryable as e_ m r advised, Data.SOP.Constraint.All ca as, cr r, GHC.Base.Monad m) => Dep.Advice.AdvisedComponent 'Dep.Advice.Terminal ca e_ m cr advised
+ Dep.Advice: instance (GHC.Base.Functor m, Dep.Advice.Gullible as e e_ m r curried) => Dep.Advice.Gullible (a : as) e e_ m r (a -> curried)
+ Dep.Advice: instance (GHC.Base.Functor m, Dep.Advice.Multicurryable as e_ m r advised) => Dep.Advice.DistributiveProduct e_ m (GHC.Generics.S1 ('GHC.Generics.MetaSel msymbol su ss ds) (GHC.Generics.Rec0 advised))
+ Dep.Advice: instance (GHC.Base.Functor m, Dep.Advice.Multicurryable as e_ m r curried) => Dep.Advice.Multicurryable (a : as) e_ m r (a -> curried)
+ Dep.Advice: instance (GHC.Generics.Generic (advised (Control.Monad.Dep.DepT e_ m)), GHC.Generics.Rep (advised (Control.Monad.Dep.DepT e_ m)) GHC.Types.~ GHC.Generics.D1 x (GHC.Generics.C1 y advised_), Data.Typeable.Internal.Typeable advised, Dep.Advice.AdvisedProduct ca e_ m cr advised_) => Dep.Advice.AdvisedRecord ca e_ m cr advised
+ Dep.Advice: instance (GHC.Generics.Generic (advised (Control.Monad.Dep.DepT e_ m)), GHC.Generics.Rep (advised (Control.Monad.Dep.DepT e_ m)) GHC.Types.~ GHC.Generics.D1 x (GHC.Generics.C1 y advised_), Dep.Advice.DistributiveProduct e_ m advised_, GHC.Base.Functor m) => Dep.Advice.DistributiveRecord e_ m advised
+ Dep.Advice: instance (GHC.Generics.Generic (gullible (Control.Monad.Trans.Reader.ReaderT e m)), GHC.Generics.Generic (gullible (Control.Monad.Dep.DepT e_ m)), GHC.Generics.Rep (gullible (Control.Monad.Trans.Reader.ReaderT e m)) GHC.Types.~ GHC.Generics.D1 x (GHC.Generics.C1 y gullible_), GHC.Generics.Rep (gullible (Control.Monad.Dep.DepT e_ m)) GHC.Types.~ GHC.Generics.D1 x (GHC.Generics.C1 y deceived_), Dep.Advice.GullibleProduct e e_ m gullible_ deceived_) => Dep.Advice.GullibleRecord e e_ m gullible
+ Dep.Advice: instance Dep.Advice.AdvisedComponent (Dep.Advice.DiscriminateAdvisedComponent advised) ca e_ m cr advised => Dep.Advice.AdvisedComponent 'Dep.Advice.IWrapped ca e_ m cr (Data.Functor.Identity.Identity advised)
+ Dep.Advice: instance Dep.Advice.AdvisedComponent (Dep.Advice.DiscriminateAdvisedComponent advised) ca e_ m cr advised => Dep.Advice.AdvisedComponent 'Dep.Advice.IWrapped ca e_ m cr (Data.SOP.BasicFunctors.I advised)
+ Dep.Advice: instance Dep.Advice.AdvisedRecord ca e_ m cr advisable => Dep.Advice.AdvisedComponent 'Dep.Advice.Recurse ca e_ m cr (advisable (Control.Monad.Dep.DepT e_ m))
+ Dep.Advice: instance Dep.Advice.GullibleComponent (Dep.Advice.DiscriminateGullibleComponent gullible) e e_ m gullible deceived => Dep.Advice.GullibleComponent 'Dep.Advice.IWrapped e e_ m (Data.Functor.Identity.Identity gullible) (Data.Functor.Identity.Identity deceived)
+ Dep.Advice: instance Dep.Advice.GullibleComponent (Dep.Advice.DiscriminateGullibleComponent gullible) e e_ m gullible deceived => Dep.Advice.GullibleComponent 'Dep.Advice.IWrapped e e_ m (Data.SOP.BasicFunctors.I gullible) (Data.SOP.BasicFunctors.I deceived)
+ Dep.Advice: instance Dep.Advice.GullibleComponent (Dep.Advice.DiscriminateGullibleComponent gullible) e e_ m gullible deceived => Dep.Advice.GullibleProduct e e_ m (GHC.Generics.S1 x (GHC.Generics.Rec0 gullible)) (GHC.Generics.S1 x (GHC.Generics.Rec0 deceived))
+ Dep.Advice: instance Dep.Advice.GullibleRecord e e_ m gullible => Dep.Advice.GullibleComponent 'Dep.Advice.Recurse e e_ m (gullible (Control.Monad.Trans.Reader.ReaderT e m)) (gullible (Control.Monad.Dep.DepT e_ m))
+ Dep.Advice: instance GHC.Base.Monad m => Dep.Advice.Gullible '[] e e_ m r (Control.Monad.Dep.DepT e_ m r)
+ Dep.Advice: instance GHC.Base.Monad m => Dep.Advice.Multicurryable '[] e_ m r (Control.Monad.Dep.DepT e_ m r)
+ Dep.Advice: instance GHC.Base.Monad m => GHC.Base.Monoid (Dep.Advice.Advice ca e_ m r)
+ Dep.Advice: instance GHC.Base.Monad m => GHC.Base.Semigroup (Dep.Advice.Advice ca e_ m r)
+ Dep.Advice: instance forall k (ca :: * -> GHC.Types.Constraint) (e_ :: (* -> *) -> *) (m :: * -> *) (cr :: * -> GHC.Types.Constraint) (advised_left :: k -> *) (advised_right :: k -> *). (Dep.Advice.AdvisedProduct ca e_ m cr advised_left, Dep.Advice.AdvisedProduct ca e_ m cr advised_right) => Dep.Advice.AdvisedProduct ca e_ m cr (advised_left GHC.Generics.:*: advised_right)
+ Dep.Advice: instance forall k (e_ :: (* -> *) -> *) (m :: * -> *) (advised_left :: k -> *) (advised_right :: k -> *). (Dep.Advice.DistributiveProduct e_ m advised_left, Dep.Advice.DistributiveProduct e_ m advised_right, GHC.Base.Functor m) => Dep.Advice.DistributiveProduct e_ m (advised_left GHC.Generics.:*: advised_right)
+ Dep.Advice: instance forall k e (e_ :: (* -> *) -> *) (m :: * -> *) (gullible_left :: k -> *) (deceived_left :: k -> *) (gullible_right :: k -> *) (deceived_right :: k -> *). (Dep.Advice.GullibleProduct e e_ m gullible_left deceived_left, Dep.Advice.GullibleProduct e e_ m gullible_right deceived_right) => Dep.Advice.GullibleProduct e e_ m (gullible_left GHC.Generics.:*: gullible_right) (deceived_left GHC.Generics.:*: deceived_right)
+ Dep.Advice: makeAdvice :: forall ca e_ m r. (forall as. All ca as => NP I as -> DepT e_ m (DepT e_ m r -> DepT e_ m r, NP I as)) -> Advice ca e_ m r
+ Dep.Advice: makeArgsAdvice :: forall ca e_ m r. Monad m => (forall as. All ca as => NP I as -> DepT e_ m (NP I as)) -> Advice ca e_ m r
+ Dep.Advice: makeExecutionAdvice :: forall ca e_ m r. Applicative m => (DepT e_ m r -> DepT e_ m r) -> Advice ca e_ m r
+ Dep.Advice: newtype I a
+ Dep.Advice: restrictArgs :: forall more less e_ m r. (forall x. Dict more x -> Dict less x) -> Advice less e_ m r -> Advice more e_ m r
+ Dep.Advice: runFinalDepT :: forall as e_ m r curried. Multicurryable as e_ m r curried => m (e_ (DepT e_ m)) -> curried -> DownToBaseMonad as e_ m r curried
+ Dep.Advice: runFromDep :: forall dep as e_ m r curried. (Multicurryable as e_ m r curried, Has dep (DepT e_ m) (e_ (DepT e_ m))) => m (e_ (DepT e_ m)) -> (dep (DepT e_ m) -> curried) -> DownToBaseMonad as e_ m r curried
+ Dep.Advice: runFromEnv :: forall as e_ m r curried. Multicurryable as e_ m r curried => m (e_ (DepT e_ m)) -> (e_ (DepT e_ m) -> curried) -> DownToBaseMonad as e_ m r curried
+ Dep.Advice: toSimple :: Monad m => Advice ca NilEnv m r -> Advice ca m r
+ Dep.Advice: type Ensure c e_ m = c (DepT e_ m) (e_ (DepT e_ m))
+ Dep.Advice.Basic: doLocally :: forall ca e_ m r. Monad m => (e_ (DepT e_ m) -> e_ (DepT e_ m)) -> Advice ca e_ m r
+ Dep.SimpleAdvice: AspectT :: m r -> AspectT (m :: Type -> Type) (r :: Type)
+ Dep.SimpleAdvice: I :: a -> I a
+ Dep.SimpleAdvice: [:*] :: forall k (a :: k -> Type) (x :: k) (xs :: [k]). a x -> NP a xs -> NP a (x : xs)
+ Dep.SimpleAdvice: [Dict] :: forall k (c :: k -> Constraint) (a :: k). c a => Dict c a
+ Dep.SimpleAdvice: [Nil] :: forall k (a :: k -> Type). NP a ('[] :: [k])
+ Dep.SimpleAdvice: [runAspectT] :: AspectT (m :: Type -> Type) (r :: Type) -> m r
+ Dep.SimpleAdvice: advise :: forall ca m r as advisee. (Multicurryable as m r advisee, All ca as, Monad m) => Advice ca m r -> advisee -> advisee
+ Dep.SimpleAdvice: adviseRecord :: forall ca cr m advised. AdvisedRecord ca m cr advised => (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca m r) -> advised (AspectT m) -> advised (AspectT m)
+ Dep.SimpleAdvice: advising :: Coercible (r_ m) (r_ (AspectT m)) => (r_ (AspectT m) -> r_ (AspectT m)) -> r_ m -> r_ m
+ Dep.SimpleAdvice: cfoldMap_NP :: forall k c (xs :: [k]) m proxy f. (All c xs, Monoid m) => proxy c -> (forall (a :: k). c a => f a -> m) -> NP f xs -> m
+ Dep.SimpleAdvice: class (AllF c xs, SListI xs) => All (c :: k -> Constraint) (xs :: [k])
+ Dep.SimpleAdvice: class (f x, g x) => And (f :: k -> Constraint) (g :: k -> Constraint) (x :: k)
+ Dep.SimpleAdvice: class Top (x :: k)
+ Dep.SimpleAdvice: data Advice (ca :: Type -> Constraint) m r
+ Dep.SimpleAdvice: data Dict (c :: k -> Constraint) (a :: k)
+ Dep.SimpleAdvice: data NP (a :: k -> Type) (b :: [k])
+ Dep.SimpleAdvice: infixl 7 `And`
+ Dep.SimpleAdvice: infixr 5 :*
+ Dep.SimpleAdvice: instance (Dep.SimpleAdvice.AdvisedComponent (Dep.SimpleAdvice.DiscriminateAdvisedComponent advised) ca m cr advised, GHC.TypeLits.KnownSymbol fieldName) => Dep.SimpleAdvice.AdvisedProduct ca m cr (GHC.Generics.S1 ('GHC.Generics.MetaSel ('GHC.Maybe.Just fieldName) su ss ds) (GHC.Generics.Rec0 advised))
+ Dep.SimpleAdvice: instance (Dep.SimpleAdvice.Multicurryable as m r advised, Data.SOP.Constraint.All ca as, cr r, GHC.Base.Monad m) => Dep.SimpleAdvice.AdvisedComponent 'Dep.SimpleAdvice.Terminal ca m cr advised
+ Dep.SimpleAdvice: instance (GHC.Generics.Generic (advised (Dep.SimpleAdvice.Internal.AspectT m)), GHC.Generics.Rep (advised (Dep.SimpleAdvice.Internal.AspectT m)) GHC.Types.~ GHC.Generics.D1 x (GHC.Generics.C1 y advised_), Data.Typeable.Internal.Typeable advised, Dep.SimpleAdvice.AdvisedProduct ca m cr advised_) => Dep.SimpleAdvice.AdvisedRecord ca m cr advised
+ Dep.SimpleAdvice: instance Dep.SimpleAdvice.AdvisedComponent (Dep.SimpleAdvice.DiscriminateAdvisedComponent advised) ca m cr advised => Dep.SimpleAdvice.AdvisedComponent 'Dep.SimpleAdvice.IWrapped ca m cr (Data.Functor.Identity.Identity advised)
+ Dep.SimpleAdvice: instance Dep.SimpleAdvice.AdvisedComponent (Dep.SimpleAdvice.DiscriminateAdvisedComponent advised) ca m cr advised => Dep.SimpleAdvice.AdvisedComponent 'Dep.SimpleAdvice.IWrapped ca m cr (Data.SOP.BasicFunctors.I advised)
+ Dep.SimpleAdvice: instance Dep.SimpleAdvice.AdvisedRecord ca m cr advisable => Dep.SimpleAdvice.AdvisedComponent 'Dep.SimpleAdvice.Recurse ca m cr (advisable (Dep.SimpleAdvice.Internal.AspectT m))
+ Dep.SimpleAdvice: instance Dep.SimpleAdvice.Multicurryable as m r curried => Dep.SimpleAdvice.Multicurryable (a : as) m r (a -> curried)
+ Dep.SimpleAdvice: instance GHC.Base.Monad m => Dep.SimpleAdvice.Multicurryable '[] m r (Dep.SimpleAdvice.Internal.AspectT m r)
+ Dep.SimpleAdvice: instance forall k (ca :: * -> GHC.Types.Constraint) (m :: * -> *) (cr :: * -> GHC.Types.Constraint) (advised_left :: k -> *) (advised_right :: k -> *). (Dep.SimpleAdvice.AdvisedProduct ca m cr advised_left, Dep.SimpleAdvice.AdvisedProduct ca m cr advised_right) => Dep.SimpleAdvice.AdvisedProduct ca m cr (advised_left GHC.Generics.:*: advised_right)
+ Dep.SimpleAdvice: makeAdvice :: forall ca m r. (forall as. All ca as => NP I as -> AspectT m (AspectT m r -> AspectT m r, NP I as)) -> Advice ca m r
+ Dep.SimpleAdvice: makeArgsAdvice :: forall ca m r. Monad m => (forall as. All ca as => NP I as -> AspectT m (NP I as)) -> Advice ca m r
+ Dep.SimpleAdvice: makeExecutionAdvice :: forall ca m r. Applicative m => (AspectT m r -> AspectT m r) -> Advice ca m r
+ Dep.SimpleAdvice: newtype AspectT (m :: Type -> Type) (r :: Type)
+ Dep.SimpleAdvice: newtype I a
+ Dep.SimpleAdvice: restrictArgs :: forall more less m r. (forall x. Dict more x -> Dict less x) -> Advice less m r -> Advice more m r
+ Dep.SimpleAdvice.Basic: SyntheticCallStackException :: SomeException -> SyntheticCallStack -> SyntheticCallStackException
+ Dep.SimpleAdvice.Basic: [AnyEq] :: forall a. (Typeable a, Eq a) => a -> AnyEq
+ Dep.SimpleAdvice.Basic: callStack :: forall f. (HasSyntheticCallStack e, Functor f) => (SyntheticCallStack -> f SyntheticCallStack) -> e -> f e
+ Dep.SimpleAdvice.Basic: class HasSyntheticCallStack e
+ Dep.SimpleAdvice.Basic: data AnyEq
+ Dep.SimpleAdvice.Basic: data SyntheticCallStackException
+ Dep.SimpleAdvice.Basic: doAsyncBadly :: forall ca m. MonadUnliftIO m => Advice ca m ()
+ Dep.SimpleAdvice.Basic: doCachingBadly :: forall m r. Monad m => (AnyEq -> m (Maybe r)) -> (AnyEq -> r -> m ()) -> Advice (Eq `And` Typeable) m r
+ Dep.SimpleAdvice.Basic: injectFailures :: forall ca m r. (MonadIO m, MonadFail m) => IORef ([IO ()], [IO ()]) -> Advice ca m r
+ Dep.SimpleAdvice.Basic: instance Dep.SimpleAdvice.Basic.HasSyntheticCallStack Dep.SimpleAdvice.Basic.SyntheticCallStack
+ Dep.SimpleAdvice.Basic: instance GHC.Classes.Eq Dep.SimpleAdvice.Basic.AnyEq
+ Dep.SimpleAdvice.Basic: instance GHC.Exception.Type.Exception Dep.SimpleAdvice.Basic.SyntheticCallStackException
+ Dep.SimpleAdvice.Basic: instance GHC.Show.Show Dep.SimpleAdvice.Basic.SyntheticCallStackException
+ Dep.SimpleAdvice.Basic: keepCallStack :: (MonadUnliftIO m, MonadReader runenv m, HasSyntheticCallStack runenv, Exception e) => (SomeException -> Maybe e) -> NonEmpty (TypeRep, MethodName) -> Advice ca m r
+ Dep.SimpleAdvice.Basic: printArgs :: forall m r. MonadIO m => Handle -> String -> Advice Show m r
+ Dep.SimpleAdvice.Basic: returnMempty :: forall ca m r. (Monad m, Monoid r) => Advice ca m r
+ Dep.SimpleAdvice.Basic: type MethodName = String
+ Dep.SimpleAdvice.Basic: type StackFrame = (TypeRep, MethodName)
+ Dep.SimpleAdvice.Basic: type SyntheticCallStack = [StackFrame]
Files
- CHANGELOG.md +10/−0
- README.md +8/−4
- dep-t-advice.cabal +18/−6
- lib/Control/Monad/Dep/Advice.hs +4/−964
- lib/Control/Monad/Dep/Advice/Basic.hs +4/−105
- lib/Control/Monad/Dep/SimpleAdvice.hs +4/−491
- lib/Control/Monad/Dep/SimpleAdvice/Basic.hs +4/−138
- lib/Control/Monad/Dep/SimpleAdvice/Internal.hs +0/−126
- lib/Dep/Advice.hs +972/−0
- lib/Dep/Advice/Basic.hs +108/−0
- lib/Dep/SimpleAdvice.hs +495/−0
- lib/Dep/SimpleAdvice/Basic.hs +238/−0
- lib/Dep/SimpleAdvice/Internal.hs +126/−0
- test/simple-advice-tests.hs +3/−3
- test/synthetic-callstack-tests.hs +349/−0
- test/tests-has-conversion.hs +4/−4
- test/tests.hs +3/−3
- test/weird-advice-tests.hs +2/−2
CHANGELOG.md view
@@ -1,5 +1,15 @@ # Revision history for dep-t-advice +## 0.5.1.0 + +* `Control.Monad.whatever` renamed to `whatever`. + + The old modules still remain, but deprecated. + +* Added `fromSimple_`. + +* Added `injectFailures` and `keepCallStack`. + ## 0.5.0.0 * The Advice type has changed to get rid of the existential type.
README.md view
@@ -6,6 +6,8 @@ functions in your record-of-functions, in a way that is composable and independent of each function's particular number of arguments. +[](https://postimg.cc/tsxKzBnv) + ## Rationale So, you have decided to structure your program in a record-of-functions style, @@ -118,20 +120,22 @@ works on functions that end on a `DepT` action. Also, advice might depend on the full gamut of functionality stored in the environment. -## What about `Control.Monad.Dep.SimpleAdvice`? +## What about `Dep.SimpleAdvice`? -`Advice`s form `Control.Monad.Dep.Advice` work with the `DepT` monad, but +`Advice`s form `Dep.Advice` work with the `DepT` monad, but that's a bit too specialized. What if I want to use plain `IO` as the monad which parameterizes my record-of-functions? -`Control.Monad.Dep.SimpleAdvice` provides a version of the `Advice` type that +`Dep.SimpleAdvice` provides a version of the `Advice` type that works with records-of-functions parameterized with `IO` or other concrete monads. This simpler `Advice` can be useful when performing dependency injection -through [`fixEnv`](https://hackage.haskell.org/package/dep-t-0.4.6.0/docs/Control-Monad-Dep-Env.html#g:9). +through [`fixEnv`](https://hackage.haskell.org/package/dep-t-0.5.0.0/docs/Control-Monad-Dep-Env.html#v:fixEnv). There are conversion functions between the two versions of `Advice`. + +See [this thread](https://discourse.haskell.org/t/decorate-your-records-of-functions-with-this-weird-trick/3675) in the Haskell Discourse for more info. ## Historical aside
dep-t-advice.cabal view
@@ -1,8 +1,8 @@ cabal-version: 3.0 name: dep-t-advice -version: 0.5.0.0 -synopsis: Giving good advice to functions in a record-of-functions. +version: 0.5.1.0 +synopsis: Giving good advice to functions in records-of-functions. description: Companion to the dep-t package. Easily add behaviour to functions living in a record-of-functions, whatever the @@ -30,7 +30,7 @@ build-depends: base >=4.10.0.0 && < 5, sop-core ^>= 0.5.0.0, transformers ^>= 0.5.0.0, - dep-t ^>= 0.5.0.0, + dep-t >= 0.5.1.0 && < 0.6, mtl ^>= 2.2, unliftio-core ^>= 0.2.0.0, default-language: Haskell2010 @@ -54,11 +54,16 @@ library import: common - exposed-modules: Control.Monad.Dep.Advice + exposed-modules: Dep.Advice + Dep.Advice.Basic + Dep.SimpleAdvice + Dep.SimpleAdvice.Basic + -- deprecated + Control.Monad.Dep.Advice Control.Monad.Dep.Advice.Basic Control.Monad.Dep.SimpleAdvice Control.Monad.Dep.SimpleAdvice.Basic - other-modules: Control.Monad.Dep.SimpleAdvice.Internal + other-modules: Dep.SimpleAdvice.Internal hs-source-dirs: lib @@ -81,13 +86,20 @@ build-depends: barbies ^>= 2.0.2.0 - test-suite tests-has-conversion import: common-tasty type: exitcode-stdio-1.0 main-is: tests-has-conversion.hs build-depends: aeson, containers + +test-suite synthetic-callstack-tests + import: common-tasty + type: exitcode-stdio-1.0 + main-is: synthetic-callstack-tests.hs + build-depends: aeson, + containers, + microlens ^>= 0.4.12.0 -- VERY IMPORTANT for doctests to work: https://stackoverflow.com/a/58027909/1364288 -- http://hackage.haskell.org/package/cabal-doctest
lib/Control/Monad/Dep/Advice.hs view
@@ -1,967 +1,7 @@-{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE ExistentialQuantification #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE FunctionalDependencies #-} -{-# LANGUAGE GADTSyntax #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE PolyKinds #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE StandaloneKindSignatures #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE UndecidableSuperClasses #-} -{-# LANGUAGE BlockArguments #-} - --- | --- This module provides the 'Advice' datatype, along for functions for creating, --- manipulating, composing and applying values of that type. --- --- 'Advice's are type-preserving transformations on 'DepT'-effectful functions of --- any number of arguments. --- --- >>> :{ --- foo0 :: DepT NilEnv IO (Sum Int) --- foo0 = pure (Sum 5) --- foo1 :: Bool -> DepT NilEnv IO (Sum Int) --- foo1 _ = foo0 --- foo2 :: Char -> Bool -> DepT NilEnv IO (Sum Int) --- foo2 _ = foo1 --- :} --- --- They work for @DepT@-actions of zero arguments: --- --- >>> advise (fromSimple \_ -> printArgs stdout "foo0") foo0 `runDepT` NilEnv --- foo0: --- <BLANKLINE> --- Sum {getSum = 5} --- --- And for functions of one or more arguments, provided they end on a @DepT@-action: --- --- >>> advise (fromSimple \_ -> printArgs stdout "foo1") foo1 False `runDepT` NilEnv --- foo1: False --- <BLANKLINE> --- Sum {getSum = 5} --- --- >>> advise (fromSimple \_ -> printArgs stdout "foo2") foo2 'd' False `runDepT` NilEnv --- foo2: 'd' False --- <BLANKLINE> --- Sum {getSum = 5} --- --- 'Advice's can also tweak the result value of functions: --- --- >>> advise (fromSimple \_ -> returnMempty @Top) foo2 'd' False `runDepT` NilEnv --- Sum {getSum = 0} --- --- And they can be combined using @Advice@'s 'Monoid' instance before being --- applied: --- --- >>> advise (fromSimple \_ -> printArgs stdout "foo2" <> returnMempty) foo2 'd' False `runDepT` NilEnv --- foo2: 'd' False --- <BLANKLINE> --- Sum {getSum = 0} --- --- Although sometimes composition might require harmonizing the constraints --- each 'Advice' places on the arguments, if they differ. -module Control.Monad.Dep.Advice - ( -- * The Advice type - Advice, - - -- * Creating Advice values - makeAdvice, - makeArgsAdvice, - makeExecutionAdvice, - - -- * Applying Advices - advise, - - -- * Constraint helpers - -- $constrainthelpers - Ensure, - - -- * Harmonizing Advice argument constraints - -- $restrict - restrictArgs, - - -- * Invocation helpers - -- $invocation - runFinalDepT, - runFromEnv, - runFromDep, - -- askFinalDepT, - -- * Making functions see a different environment - deceive, - - -- * Advising and deceiving entire records - -- $records - adviseRecord, - deceiveRecord, - -- * Plugging Has-based constructors - component, - --distributeDepT, - - -- * Interfacing with "simple" advices - toSimple, - fromSimple, - - -- * "sop-core" re-exports - -- $sop - Top, - And, - All, - NP (..), - I (..), - cfoldMap_NP, - Dict (..), +module Control.Monad.Dep.Advice {-# DEPRECATED "Renamed to Dep.Advice" #-} + ( + module Dep.Advice ) where -import Control.Monad.Dep -import Control.Monad.Dep.Has -import Control.Monad.Dep.Env -import Control.Monad.Trans.Reader (ReaderT (..), withReaderT) -import Data.Functor.Identity -import Data.Kind -import Data.List.NonEmpty qualified as N -import Data.List.NonEmpty (NonEmpty) -import Data.SOP -import Data.SOP.Dict -import Data.SOP.NP -import Data.Typeable -import GHC.Generics qualified as G -import GHC.TypeLits -import Data.Coerce -import Data.Bifunctor (first) -import Control.Monad.Dep.SimpleAdvice.Internal qualified as SA - --- $setup --- --- >>> :set -XTypeApplications --- >>> :set -XStandaloneKindSignatures --- >>> :set -XMultiParamTypeClasses --- >>> :set -XFunctionalDependencies --- >>> :set -XRankNTypes --- >>> :set -XTypeOperators --- >>> :set -XConstraintKinds --- >>> :set -XNamedFieldPuns --- >>> :set -XFlexibleContexts --- >>> :set -XDerivingStrategies --- >>> :set -XGeneralizedNewtypeDeriving --- >>> :set -XDataKinds --- >>> :set -XScopedTypeVariables --- >>> :set -XDeriveGeneric --- >>> :set -XImportQualifiedPost --- >>> import Control.Monad --- >>> import Control.Monad.Dep --- >>> import Control.Monad.Dep.Advice --- >>> import Control.Monad.Dep.SimpleAdvice.Basic (printArgs,returnMempty) --- >>> import Control.Monad.Writer --- >>> import Data.Kind --- >>> import Data.SOP --- >>> import Data.SOP.NP --- >>> import Data.Monoid --- >>> import System.IO --- >>> import Data.IORef --- >>> import GHC.Generics (Generic) --- >>> import GHC.Generics qualified - --- | A generic transformation of 'DepT'-effectful functions with environment --- @e_@, base monad @m@ and return type @r@, --- provided the functions satisfy certain constraint @ca@ --- on all of their arguments. --- --- Note that the type constructor for the environment @e_@ is given unapplied. --- That is, @Advice Show NilEnv IO ()@ kind-checks but @Advice Show (NilEnv IO) --- IO ()@ doesn't. See also 'Ensure'. --- --- 'Advice's that don't care about the @ca@ constraint (because they don't --- touch function arguments) can leave it polymorphic, and this facilitates --- 'Advice' composition, but then the constraint must be given the catch-all --- `Top` value (using a type application) at the moment of calling 'advise'. --- --- See "Control.Monad.Dep.Advice.Basic" for examples. -type Advice :: - (Type -> Constraint) -> - ((Type -> Type) -> Type) -> - (Type -> Type) -> - Type -> - Type -data Advice (ca :: Type -> Constraint) (e_ :: (Type -> Type) -> Type) m r where - Advice :: - forall ca e_ m r. - ( forall as. - All ca as => - NP I as -> - DepT e_ m (DepT e_ m r -> DepT e_ m r, NP I as) - ) -> - Advice ca e_ m r - --- | --- 'Advice's compose \"sequentially\" when tweaking the arguments, and --- \"concentrically\" when tweaking the final 'DepT' action. --- --- The first 'Advice' is the \"outer\" one. It tweaks the function arguments --- first, and wraps around the execution of the second, \"inner\" 'Advice'. -instance Monad m => Semigroup (Advice ca e_ m r) where - Advice outer <> Advice inner = Advice \args -> do - (tweakOuter, argsOuter) <- outer args - (tweakInner, argsInner) <- inner argsOuter - pure (tweakOuter . tweakInner, argsInner) - -instance Monad m => Monoid (Advice ca e_ m r) where - mappend = (<>) - mempty = Advice \args -> pure (id, args) - --- | --- The most general way of constructing 'Advice's. --- --- An 'Advice' is a function that transforms other functions in an --- arity-polymorphic way. It receives the arguments of the advised --- function packed into an n-ary product 'NP', performs some --- effects based on them, and returns a potentially modified version of the --- arguments, along with a function for tweaking the execution of the --- advised function. --- --- >>> :{ --- doesNothing :: forall ca e_ m r. Monad m => Advice ca e_ m r --- doesNothing = makeAdvice (\args -> pure (id, args)) --- :} --- --- -makeAdvice :: - forall ca e_ m r. - -- | The function that tweaks the arguments and the execution. - ( forall as. - All ca as => - NP I as -> - DepT e_ m (DepT e_ m r -> DepT e_ m r, NP I as) - ) -> - Advice ca e_ m r -makeAdvice = Advice - --- | --- Create an advice which only tweaks and/or analyzes the function arguments. --- --- >>> :{ --- doesNothing :: forall ca e_ m r. Monad m => Advice ca e_ m r --- doesNothing = makeArgsAdvice pure --- :} -makeArgsAdvice :: - forall ca e_ m r. - Monad m => - -- | The function that tweaks the arguments. - ( forall as. - All ca as => - NP I as -> - DepT e_ m (NP I as) - ) -> - Advice ca e_ m r -makeArgsAdvice tweakArgs = - makeAdvice $ \args -> do - args' <- tweakArgs args - pure (id, args') - --- | --- Create an advice which only tweaks the execution of the final monadic action. --- --- >>> :{ --- doesNothing :: forall ca e_ m r. Monad m => Advice ca e_ m r --- doesNothing = makeExecutionAdvice id --- :} -makeExecutionAdvice :: - forall ca e_ m r. - Applicative m => - -- | The function that tweaks the execution. - ( DepT e_ m r -> - DepT e_ m r - ) -> - Advice ca e_ m r -makeExecutionAdvice tweakExecution = makeAdvice \args -> pure (tweakExecution, args) - -data Pair a b = Pair !a !b - --- | --- 'Ensure' is a helper for lifting typeclass definitions of the form: --- --- >>> :{ --- type HasLogger :: (Type -> Type) -> Type -> Constraint --- class HasLogger d e | e -> d where --- logger :: e -> String -> d () --- :} --- --- To work as a constraints on the @e_@ and @m@ parameters of an 'Advice', like this: --- --- >>> :{ --- requiresLogger :: forall e_ m r. (Ensure HasLogger e_ m, Monad m) => Advice Show e_ m r --- requiresLogger = mempty --- :} --- --- Why is it necessary? Two-place @HasX@-style constraints receive the \"fully --- applied\" type of the record-of-functions. That is: @NilEnv IO@ instead of --- simply @NilEnv@. This allows them to also work with monomorphic environments --- (like those in <http://hackage.haskell.org/package/rio RIO>) whose type --- isn't parameterized by any monad. --- --- But the @e_@ type parameter of 'Advice' has kind @(Type -> Type) -> Type@. --- That is: @NilEnv@ alone. --- --- Furthermore, 'Advices' require @HasX@-style constraints to be placed on the --- @DepT@ transformer, not directly on the base monad @m@. @Ensure@ takes care --- of that as well. -type Ensure :: ((Type -> Type) -> Type -> Constraint) -> ((Type -> Type) -> Type) -> (Type -> Type) -> Constraint - -type Ensure c e_ m = c (DepT e_ m) (e_ (DepT e_ m)) - --- | Apply an 'Advice' to some compatible function. The function must have its --- effects in 'DepT', and all of its arguments must satisfy the @ca@ constraint. --- --- >>> :{ --- foo :: Int -> DepT NilEnv IO String --- foo _ = pure "foo" --- advisedFoo = advise (fromSimple \_ -> printArgs stdout "Foo args: ") foo --- :} --- --- __/TYPE APPLICATION REQUIRED!/__ If the @ca@ constraint of the 'Advice' remains polymorphic, --- it must be supplied by means of a type application: --- --- >>> :{ --- bar :: Int -> DepT NilEnv IO String --- bar _ = pure "bar" --- advisedBar1 = advise (fromSimple \_ -> returnMempty @Top) bar --- advisedBar2 = advise @Top (fromSimple \_ -> returnMempty) bar --- :} -advise :: - forall ca e_ m r as advisee. - (Multicurryable as e_ m r advisee, All ca as, Monad m) => - -- | The advice to apply. - Advice ca e_ m r -> - -- | A function to be adviced. - advisee -> - advisee -advise (Advice f) advisee = do - let uncurried = multiuncurry @as @e_ @m @r advisee - uncurried' args = do - (tweakExecution, args') <- f args - tweakExecution (uncurried args') - in multicurry @as @e_ @m @r uncurried' - -type Multicurryable :: - [Type] -> - ((Type -> Type) -> Type) -> - (Type -> Type) -> - Type -> - Type -> - Constraint -class Multicurryable as e_ m r curried | curried -> as e_ m r where - type DownToBaseMonad as e_ m r curried :: Type - multiuncurry :: curried -> NP I as -> DepT e_ m r - multicurry :: (NP I as -> DepT e_ m r) -> curried - _runFromEnv :: m (e_ (DepT e_ m)) -> (e_ (DepT e_ m) -> curried) -> DownToBaseMonad as e_ m r curried - _askFinalDepT :: (e_ (DepT e_ m) -> m curried) -> curried - -instance Monad m => Multicurryable '[] e_ m r (DepT e_ m r) where - type DownToBaseMonad '[] e_ m r (DepT e_ m r) = m r - multiuncurry action Nil = action - multicurry f = f Nil - _runFromEnv producer extractor = do - e <- producer - runDepT (extractor e) e - _askFinalDepT f = do - env <- ask - r <- lift (f env) - r - -instance (Functor m, Multicurryable as e_ m r curried) => Multicurryable (a ': as) e_ m r (a -> curried) where - type DownToBaseMonad (a ': as) e_ m r (a -> curried) = a -> DownToBaseMonad as e_ m r curried - multiuncurry f (I a :* as) = multiuncurry @as @e_ @m @r @curried (f a) as - multicurry f a = multicurry @as @e_ @m @r @curried (f . (:*) (I a)) - _runFromEnv producer extractor a = _runFromEnv @as @e_ @m @r @curried producer (\f -> extractor f a) - _askFinalDepT f = - let switcheroo action a = fmap ($ a) action - in _askFinalDepT @as @e_ @m @r . flip (fmap switcheroo f) - --- | Given a base monad @m@ action that gets hold of the 'DepT' environment, run --- the 'DepT' transformer at the tip of a curried function. --- --- >>> :{ --- foo :: Int -> Int -> Int -> DepT NilEnv IO () --- foo _ _ _ = pure () --- :} --- --- >>> runFinalDepT (pure NilEnv) foo 1 2 3 :: IO () -runFinalDepT :: - forall as e_ m r curried. - Multicurryable as e_ m r curried => - -- | action that gets hold of the environment - m (e_ (DepT e_ m)) -> - -- | function to invoke with effects in 'DepT' - curried -> - -- | a new function with effects in the base monad - DownToBaseMonad as e_ m r curried -runFinalDepT producer extractor = _runFromEnv producer (const extractor) - -askFinalDepT :: - forall as e_ m r curried. - Multicurryable as e_ m r curried => - (e_ (DepT e_ m) -> m curried) -> curried -askFinalDepT = _askFinalDepT @as @e_ @m @r - --- | Given a base monad @m@ action that gets hold of the 'DepT' environment, --- and a function capable of extracting a curried function from the --- environment, run the 'DepT' transformer at the tip of the resulting curried --- function. --- --- Why put the environment behind the @m@ action? Well, since getting to the --- end of the curried function takes some work, it's a good idea to have some --- flexibility once we arrive there. For example, the environment could be --- stored in a "Data.IORef" and change in response to events, perhaps with --- advices being added or removed. --- --- >>> :{ --- type MutableEnv :: (Type -> Type) -> Type --- data MutableEnv m = MutableEnv { _foo :: Int -> m (Sum Int) } --- :} --- --- >>> :{ --- do envRef <- newIORef (MutableEnv (pure . Sum)) --- let foo' = runFromEnv (readIORef envRef) _foo --- do r <- foo' 7 --- print r --- modifyIORef envRef (\e -> e { _foo = advise @Top (fromSimple \_ -> returnMempty) (_foo e) }) --- do r <- foo' 7 --- print r --- :} --- Sum {getSum = 7} --- Sum {getSum = 0} -runFromEnv :: - forall as e_ m r curried. - Multicurryable as e_ m r curried => - -- | action that gets hold of the environment - m (e_ (DepT e_ m)) -> - -- | gets a function from the environment with effects in 'DepT' - (e_ (DepT e_ m) -> curried) -> - -- | a new function with effects in the base monad - DownToBaseMonad as e_ m r curried -runFromEnv = _runFromEnv - --- | Like 'runFromEnv', but the function to run is extracted from a dependency --- @dep@ which is found using 'Has'. The selector should be concrete enough to --- identify @dep@ in the environment. -runFromDep :: - forall dep as e_ m r curried. - (Multicurryable as e_ m r curried, Has dep (DepT e_ m) (e_ (DepT e_ m))) => - -- | action that gets hold of the environment - m (e_ (DepT e_ m)) -> - -- | selector that gets a function from a dependency found using 'Has' - (dep (DepT e_ m) -> curried) -> - -- | a new function with effects in the base monad - DownToBaseMonad as e_ m r curried -runFromDep envAction member = _runFromEnv envAction (member . dep) - --- $restrict --- --- 'Advice' values can be composed using the 'Monoid' instance, but only if --- they have the same type parameters. It's unfortunate that—unlike with --- normal function constraints—the @ca@ constraints of an 'Advice' aren't --- automatically "collected" during composition. --- --- Instead, we need to harmonize the @ca@ constraints of each 'Advice' by --- turning them into the combination of all constraints. 'restrictArgs' --- helps with that. --- --- 'restrictArgs' takes as parameter value-level "\evidence\" that one --- constraint implies another. But how to construct such evidence? By using --- the 'Dict' GADT, more precisely the deceptively simple-looking term --- @\\Dict -> Dict@. That function "absorbs" some constraint present in the --- ambient context and re-packages it a a new constraint that is implied by --- the former. We can't rely on type inference here; we need to provide --- enough type information to the GADT, be it as an explicit signature: --- --- >>> :{ --- stricterPrintArgs :: forall e_ m r. MonadIO m => Advice (Show `And` Eq `And` Ord) e_ m r --- stricterPrintArgs = restrictArgs (\Dict -> Dict) (fromSimple \_ -> printArgs stdout "foo") --- :} --- --- or with a type application to 'restrictArgs': --- --- >>> stricterPrintArgs = restrictArgs @(Show `And` Eq `And` Ord) (\Dict -> Dict) (fromSimple \_ -> printArgs stdout "foo") - --- | Makes the constraint on the arguments more restrictive. -restrictArgs :: - forall more less e_ m r. - -- | Evidence that one constraint implies the other. Every @x@ that has a @more@ instance also has a @less@ instance. - (forall x. Dict more x -> Dict less x) -> - -- | Advice with less restrictive constraint on the args. - Advice less e_ m r -> - -- | Advice with more restrictive constraint on the args. - Advice more e_ m r --- about the order of the type parameters... which is more useful? --- A possible principle to follow: --- We are likely to know the "less" constraint, because advices are likely to --- come pre-packaged and having a type signature. --- We arent' so sure about having a signature for a whole composed Advice, --- because the composition might be done --- on the fly, while constructing a record, without a top-level binding with a --- type signature. This seems to favor putting "more" first. -restrictArgs evidence (Advice advice) = Advice \args -> - let advice' :: forall as. All more as => NP I as -> DepT e_ m (DepT e_ m r -> DepT e_ m r, NP I as) - advice' args' = - case Data.SOP.Dict.mapAll @more @less evidence of - f -> case f (Dict @(All more) @as) of - Dict -> advice args' - in advice' args - --- -type Gullible :: - [Type] -> - Type -> - ((Type -> Type) -> Type) -> - (Type -> Type) -> - Type -> - Type -> - Constraint -class Multicurryable as e_ m r curried => Gullible as e e_ m r curried where - type NewtypedEnv as e e_ m r curried :: Type - _deceive :: (e_ (DepT e_ m) -> e) -> NewtypedEnv as e e_ m r curried -> curried - -instance Monad m => Gullible '[] e e_ m r (DepT e_ m r) where - type NewtypedEnv '[] e e_ m r (DepT e_ m r) = ReaderT e m r - _deceive f action = DepT (withReaderT f action) - -instance (Functor m, Gullible as e e_ m r curried) => Gullible (a ': as) e e_ m r (a -> curried) where - type NewtypedEnv (a ': as) e e_ m r (a -> curried) = a -> NewtypedEnv as e e_ m r curried - _deceive f g a = deceive @as @e @e_ @m @r f (g a) - --- | Makes a function see a newtyped version of the environment record, a version that might have different @HasX@ instances. --- --- The \"deception\" doesn't affect the dependencies used by the function, only the function itself. --- --- For example, consider the following setup which features both a \"deceviced\" --- and an \"undeceived\" version of a controller function: --- --- >>> :{ --- type HasLogger :: (Type -> Type) -> Type -> Constraint --- class HasLogger d e | e -> d where --- logger :: e -> String -> d () --- type HasIntermediate :: (Type -> Type) -> Type -> Constraint --- class HasIntermediate d e | e -> d where --- intermediate :: e -> String -> d () --- type Env :: (Type -> Type) -> Type --- data Env m = Env --- { _logger1 :: String -> m (), --- _logger2 :: String -> m (), --- _intermediate :: String -> m (), --- _controllerA :: Int -> m (), --- _controllerB :: Int -> m () --- } --- instance HasLogger m (Env m) where --- logger = _logger1 --- instance HasIntermediate m (Env m) where --- intermediate = _intermediate --- newtype Switcheroo m = Switcheroo (Env m) --- deriving newtype (HasIntermediate m) --- instance HasLogger m (Switcheroo m) where --- logger (Switcheroo e) = _logger2 e --- env :: Env (DepT Env (Writer [String])) --- env = --- let mkController :: forall d e m. MonadDep [HasLogger, HasIntermediate] d e m => Int -> m () --- mkController _ = do e <- ask; liftD $ logger e "foo" ; liftD $ intermediate e "foo" --- mkIntermediate :: forall d e m. MonadDep '[HasLogger] d e m => String -> m () --- mkIntermediate _ = do e <- ask ; liftD $ logger e "foo" --- in Env --- { --- _logger1 = --- \_ -> tell ["logger 1"], --- _logger2 = --- \_ -> tell ["logger 2"], --- _intermediate = --- mkIntermediate, --- _controllerA = --- mkController, --- _controllerB = --- deceive Switcheroo $ --- mkController --- } --- :} --- --- If we run @_controllerA@ the ouput is: --- --- >>> execWriter $ runFromEnv (pure env) _controllerA 7 --- ["logger 1","logger 1"] --- --- But if we run the \"deceived\" @_controllerB@, we see that the function and its @_intermediate@ dependency use different loggers: --- --- >>> execWriter $ runFromEnv (pure env) _controllerB 7 --- ["logger 2","logger 1"] --- --- Note that the function that is \"deceived\" must be polymorphic over --- 'Control.Monad.Dep.MonadDep'. Passing a function whose effect monad has --- already \"collapsed\" into 'DepT' won't work. Therefore, 'deceive' must be applied before any 'Advice'. -deceive :: - forall as e e_ m r curried. - Gullible as e e_ m r curried => - -- | The newtype constructor that masks the \"true\" environment. - (e_ (DepT e_ m) -> e) -> - -- | A function to be deceived. It must be polymorphic over 'Control.Monad.Dep.MonadDep'. - NewtypedEnv as e e_ m r curried -> - -- | The deceived function, that has effects in 'DepT'. - curried -deceive = _deceive - --- deceving *all* fields of a record --- --- -type GullibleRecord :: Type -> ((Type -> Type) -> Type) -> (Type -> Type) -> ((Type -> Type) -> Type) -> Constraint -class GullibleRecord e e_ m gullible where - _deceiveRecord :: (e_ (DepT e_ m) -> e) -> gullible (ReaderT e m) -> gullible (DepT e_ m) - --- https://gitlab.haskell.org/ghc/ghc/-/issues/13952 -type GullibleProduct :: Type -> ((Type -> Type) -> Type) -> (Type -> Type) -> (k -> Type) -> (k -> Type) -> Constraint -class GullibleProduct e e_ m gullible_ deceived_ | e e_ m deceived_ -> gullible_ where - _deceiveProduct :: (e_ (DepT e_ m) -> e) -> gullible_ k -> deceived_ k - -instance - ( GullibleProduct e e_ m gullible_left deceived_left, - GullibleProduct e e_ m gullible_right deceived_right - ) => - GullibleProduct e e_ m (gullible_left G.:*: gullible_right) (deceived_left G.:*: deceived_right) - where - _deceiveProduct f (gullible_left G.:*: gullible_right) = _deceiveProduct @_ @e @e_ @m f gullible_left G.:*: _deceiveProduct @_ @e @e_ @m f gullible_right - -data RecordComponent - = Terminal - | IWrapped - | Recurse - -type DiscriminateGullibleComponent :: Type -> RecordComponent -type family DiscriminateGullibleComponent c where - DiscriminateGullibleComponent (a -> b) = Terminal - DiscriminateGullibleComponent (ReaderT e m x) = Terminal - DiscriminateGullibleComponent (Identity _) = IWrapped - DiscriminateGullibleComponent (I _) = IWrapped - DiscriminateGullibleComponent _ = Recurse - -type GullibleComponent :: RecordComponent -> Type -> ((Type -> Type) -> Type) -> (Type -> Type) -> Type -> Type -> Constraint -class GullibleComponent component_type e e_ m gullible deceived | e e_ m deceived -> gullible where - _deceiveComponent :: (e_ (DepT e_ m) -> e) -> gullible -> deceived - -instance - (Gullible as e e_ m r deceived, NewtypedEnv as e e_ m r deceived ~ gullible) => - GullibleComponent Terminal e e_ m gullible deceived - where - _deceiveComponent f gullible = deceive @as @e @_ @m @r f gullible - -instance - GullibleComponent (DiscriminateGullibleComponent gullible) e e_ m gullible deceived => - GullibleComponent IWrapped e e_ m (Identity gullible) (Identity deceived) - where - _deceiveComponent f (Identity gullible) = Identity (_deceiveComponent @(DiscriminateGullibleComponent gullible) @e @e_ @m f gullible) - -instance - GullibleComponent (DiscriminateGullibleComponent gullible) e e_ m gullible deceived => - GullibleComponent IWrapped e e_ m (I gullible) (I deceived) - where - _deceiveComponent f (I gullible) = I (_deceiveComponent @(DiscriminateGullibleComponent gullible) @e @e_ @m f gullible) - -instance - GullibleRecord e e_ m gullible => - GullibleComponent Recurse e e_ m (gullible (ReaderT e m)) (gullible (DepT e_ m)) - where - _deceiveComponent f gullible = _deceiveRecord @e @e_ @m f gullible - -instance - GullibleComponent (DiscriminateGullibleComponent gullible) e e_ m gullible deceived => - GullibleProduct e e_ m (G.S1 x (G.Rec0 gullible)) (G.S1 x (G.Rec0 deceived)) - where - _deceiveProduct f (G.M1 (G.K1 gullible)) = G.M1 (G.K1 (_deceiveComponent @(DiscriminateGullibleComponent gullible) @e @e_ @m f gullible)) - -instance - ( G.Generic (gullible (ReaderT e m)), - G.Generic (gullible (DepT e_ m)), - G.Rep (gullible (ReaderT e m)) ~ G.D1 x (G.C1 y gullible_), - G.Rep (gullible (DepT e_ m)) ~ G.D1 x (G.C1 y deceived_), - GullibleProduct e e_ m gullible_ deceived_ - ) => - GullibleRecord e e_ m gullible - where - _deceiveRecord f gullible = - let G.M1 (G.M1 gullible_) = G.from gullible - deceived_ = _deceiveProduct @_ @e @e_ @m f gullible_ - in G.to (G.M1 (G.M1 deceived_)) - --- | Makes an entire record-of-functions see a different version of the global environment record, a version that might have different @HasX@ instances. --- --- 'deceiveRecord' must be applied before 'adviseRecord'. -deceiveRecord :: - forall e e_ m gullible. - GullibleRecord e e_ m gullible => - -- | The newtype constructor that masks the \"true\" environment. - (e_ (DepT e_ m) -> e) -> - -- | The record to deceive recursively. The monad parameter must be left polymorphic over @MonadDep@, so that it can unify with `ReaderT`. - gullible (ReaderT e m) -> - -- | The deceived record. - gullible (DepT e_ m) -deceiveRecord = _deceiveRecord @e @e_ @m @gullible - - --- | Given a constructor that returns a record-of-functions with effects in 'DepT', --- produce a record in which the member functions 'ask' for the environment themselves. --- --- You must have a sufficiently polymorphic constructor—both in the monad and --- in the environment—to invoke this function. --- --- 'component' lets you plug simple component constructors --- into a 'DepT'-based environment. --- --- Compare with 'Control.Monad.Dep.Env.constructor' from "Control.Monad.Dep.Env", which --- is intended to be used with 'Control.Monad.Dep.Env.fixEnv'-based environments. -component - :: forall e_ m record . (Applicative m, DistributiveRecord e_ m record) => - -- | constructor which takes the environment as a positional parameter. - (e_ (DepT e_ m) -> record (DepT e_ m)) -> - -- | component whose methods get the environment by 'ask'ing. - record (DepT e_ m) -component f = _distribute @e_ @m (pure . f) - - - -type DistributiveRecord :: ((Type -> Type) -> Type) -> (Type -> Type) -> ((Type -> Type) -> Type) -> Constraint -class DistributiveRecord e_ m record where - _distribute :: (e_ (DepT e_ m) -> m (record (DepT e_ m))) -> record (DepT e_ m) - -type DistributiveProduct :: ((Type -> Type) -> Type) -> (Type -> Type) -> (k -> Type) -> Constraint -class DistributiveProduct e_ m product where - _distributeProduct :: (e_ (DepT e_ m) -> m (product k)) -> product k - -instance - ( G.Generic (advised (DepT e_ m)), - G.Rep (advised (DepT e_ m)) ~ G.D1 x (G.C1 y advised_), - DistributiveProduct e_ m advised_, - Functor m - ) => - DistributiveRecord e_ m advised - where - _distribute f = - let advised_ = _distributeProduct @_ @e_ @m (fmap (fmap (G.unM1 . G.unM1 . G.from)) f) - in G.to (G.M1 (G.M1 advised_)) - -instance - ( DistributiveProduct e_ m advised_left, - DistributiveProduct e_ m advised_right, - Functor m - ) => - DistributiveProduct e_ m (advised_left G.:*: advised_right) - where - _distributeProduct f = - _distributeProduct @_ @e_ @m (fmap (fmap (\(l G.:*: _) -> l)) f) - G.:*: - _distributeProduct @_ @e_ @m (fmap (fmap (\(_ G.:*: r) -> r)) f) - -instance - ( - Functor m, - Multicurryable as e_ m r advised - ) => - DistributiveProduct e_ m (G.S1 ( 'G.MetaSel msymbol su ss ds) (G.Rec0 advised)) - where - _distributeProduct f = G.M1 . G.K1 $ askFinalDepT @as @e_ @m @r (fmap (fmap (G.unK1 . G.unM1)) f) - --- advising *all* fields of a record --- --- -type AdvisedRecord :: (Type -> Constraint) -> ((Type -> Type) -> Type) -> (Type -> Type) -> (Type -> Constraint) -> ((Type -> Type) -> Type) -> Constraint -class AdvisedRecord ca e_ m cr advised where - _adviseRecord :: [(TypeRep, String)] -> (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca e_ m r) -> advised (DepT e_ m) -> advised (DepT e_ m) - -type AdvisedProduct :: (Type -> Constraint) -> ((Type -> Type) -> Type) -> (Type -> Type) -> (Type -> Constraint) -> (k -> Type) -> Constraint -class AdvisedProduct ca e_ m cr advised_ where - _adviseProduct :: TypeRep -> [(TypeRep, String)] -> (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca e_ m r) -> advised_ k -> advised_ k - -instance - ( G.Generic (advised (DepT e_ m)), - -- G.Rep (advised (DepT e_ m)) ~ G.D1 ('G.MetaData name mod p nt) (G.C1 y advised_), - G.Rep (advised (DepT e_ m)) ~ G.D1 x (G.C1 y advised_), - Typeable advised, - AdvisedProduct ca e_ m cr advised_ - ) => - AdvisedRecord ca e_ m cr advised - where - _adviseRecord acc f unadvised = - let G.M1 (G.M1 unadvised_) = G.from unadvised - advised_ = _adviseProduct @_ @ca @e_ @m @cr (typeRep (Proxy @advised)) acc f unadvised_ - in G.to (G.M1 (G.M1 advised_)) - -instance - ( AdvisedProduct ca e_ m cr advised_left, - AdvisedProduct ca e_ m cr advised_right - ) => - AdvisedProduct ca e_ m cr (advised_left G.:*: advised_right) - where - _adviseProduct tr acc f (unadvised_left G.:*: unadvised_right) = _adviseProduct @_ @ca @e_ @m @cr tr acc f unadvised_left G.:*: _adviseProduct @_ @ca @e_ @m @cr tr acc f unadvised_right - -type DiscriminateAdvisedComponent :: Type -> RecordComponent -type family DiscriminateAdvisedComponent c where - DiscriminateAdvisedComponent (a -> b) = Terminal - DiscriminateAdvisedComponent (DepT e_ m x) = Terminal - DiscriminateAdvisedComponent (Identity _) = IWrapped - DiscriminateAdvisedComponent (I _) = IWrapped - DiscriminateAdvisedComponent _ = Recurse - -type AdvisedComponent :: RecordComponent -> (Type -> Constraint) -> ((Type -> Type) -> Type) -> (Type -> Type) -> (Type -> Constraint) -> Type -> Constraint -class AdvisedComponent component_type ca e_ m cr advised where - _adviseComponent :: [(TypeRep, String)] -> (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca e_ m r) -> advised -> advised - -instance - ( AdvisedComponent (DiscriminateAdvisedComponent advised) ca e_ m cr advised, - KnownSymbol fieldName - ) => - AdvisedProduct ca e_ m cr (G.S1 ( 'G.MetaSel ( 'Just fieldName) su ss ds) (G.Rec0 advised)) - where - _adviseProduct tr acc f (G.M1 (G.K1 advised)) = - let acc' = (tr, symbolVal (Proxy @fieldName)) : acc - in G.M1 (G.K1 (_adviseComponent @(DiscriminateAdvisedComponent advised) @ca @e_ @m @cr acc' f advised)) - -instance - AdvisedRecord ca e_ m cr advisable => - AdvisedComponent Recurse ca e_ m cr (advisable (DepT e_ m)) - where - _adviseComponent acc f advised = _adviseRecord @ca @e_ @m @cr acc f advised - -instance - (Multicurryable as e_ m r advised, All ca as, cr r, Monad m) => - AdvisedComponent Terminal ca e_ m cr advised - where - _adviseComponent acc f advised = advise @ca @e_ @m (f (N.fromList acc)) advised - -instance - AdvisedComponent (DiscriminateAdvisedComponent advised) ca e_ m cr advised => - AdvisedComponent IWrapped ca e_ m cr (Identity advised) - where - _adviseComponent acc f (Identity advised) = Identity (_adviseComponent @(DiscriminateAdvisedComponent advised) @ca @e_ @m @cr acc f advised) - -instance - AdvisedComponent (DiscriminateAdvisedComponent advised) ca e_ m cr advised => - AdvisedComponent IWrapped ca e_ m cr (I advised) - where - _adviseComponent acc f (I advised) = I (_adviseComponent @(DiscriminateAdvisedComponent advised) @ca @e_ @m @cr acc f advised) - --- | Gives 'Advice' to all the functions in a record-of-functions. --- --- The function that builds the advice receives a list of tuples @(TypeRep, String)@ --- which represent the record types and fields names we have --- traversed until arriving at the advised function. This info can be useful for --- logging advices. It's a list instead of a single tuple because --- 'adviseRecord' works recursively. The elements come innermost-first. --- --- __/TYPE APPLICATION REQUIRED!/__ The @ca@ constraint on function arguments --- and the @cr@ constraint on the result type must be supplied by means of a --- type application. Supply 'Top' if no constraint is required. -adviseRecord :: - forall ca cr e_ m advised. - AdvisedRecord ca e_ m cr advised => - -- | The advice to apply. - (forall r . cr r => NonEmpty (TypeRep, String) -> Advice ca e_ m r) -> - -- | The record to advise recursively. - advised (DepT e_ m) -> - -- | The advised record. - advised (DepT e_ m) -adviseRecord = _adviseRecord @ca @e_ @m @cr [] - --- $records --- --- 'adviseRecord' and 'deceiveRecord' are versions of 'advise' and 'deceive' that, instead of working on bare --- functions, transform entire records-of-functions in one go. They also work --- with newtypes containing a single function. The records must derive 'GHC.Generics.Generic'. --- --- Useful with the \"wrapped\" style of components facilitated by @Control.Monad.Dep.Has@. --- --- >>> :{ --- type Logger :: (Type -> Type) -> Type --- newtype Logger d = Logger {log :: String -> d ()} deriving Generic --- type Repository :: (Type -> Type) -> Type --- data Repository d = Repository --- { select :: String -> d [Int], --- insert :: [Int] -> d () --- } deriving Generic --- type Controller :: (Type -> Type) -> Type --- newtype Controller d = Controller {serve :: Int -> d String} deriving Generic --- type Env :: (Type -> Type) -> Type --- data Env m = Env --- { logger :: Logger m, --- repository :: Repository m, --- controller :: Controller m --- } --- newtype Wraps x = Wraps x --- env :: Env (DepT Env (Writer ())) --- env = --- let logger = Logger \_ -> pure () --- repository = --- adviseRecord @Top @Top mempty $ --- deceiveRecord Wraps $ --- Repository {select = \_ -> pure [], insert = \_ -> pure ()} --- controller = --- adviseRecord @Top @Top mempty $ --- deceiveRecord Wraps $ --- Controller \_ -> pure "view" --- in Env {logger, repository, controller} --- :} - --- $sop --- Some useful definitions re-exported the from \"sop-core\" package. --- --- 'NP' is an n-ary product used to represent the arguments of advised functions. --- --- 'I' is an identity functor. The arguments processed by an 'Advice' come wrapped in it. --- --- 'cfoldMap_NP' is useful to construct homogeneous lists out of the 'NP' product, for example: --- --- >>> cfoldMap_NP (Proxy @Show) (\(I a) -> [show a]) (I False :* I (1::Int) :* Nil) --- ["False","1"] - --- $constraints --- --- Some useful definitions re-exported the from \"constraints\" package. --- --- 'Dict' and '(:-)' are GADTs used to capture and transform constraints. Used in the 'restrictArgs' function. - --- $constrainthelpers --- --- To help with the constraint @ca@ that parameterizes 'Advice', this library re-exports the following helpers from \"sop-core\": --- --- * 'Top' is the \"always satisfied\" constraint, useful when whe don't want to require anything specific in @ca@. --- --- * 'And' combines two constraints so that an 'Advice' can request them both, for example @Show \`And\` Eq@. --- --- Also, the 'All' constraint says that some constraint is satisfied by all the --- components of an 'NP' product. It's in scope when processing the function --- arguments inside an 'Advice'. - --- $invocation --- These functions are helpers for running 'DepT' computations, beyond what 'runDepT' provides. --- --- They aren't directly related to 'Advice's, but they require some of the same machinery, and that's why they are here. - --- | An advice that is polymorphic on the environment (allowing it to unify --- with 'Control.Monad.Dep.NilEnv') can be converted to a "simple" 'Control.Monad.Dep.SimpleAdvice.Advice' that doesn't require 'Control.Monad.Dep.DepT' at all. -toSimple :: Monad m => Advice ca NilEnv m r -> SA.Advice ca m r -toSimple (Advice f) = SA.Advice \args -> lift do - (withExecution, args') <- f args `runDepT` NilEnv - let withExecution' = lift . flip runDepT NilEnv . withExecution . lift . SA.runAspectT - pure (withExecution', args') - --- | Convert a simple 'Control.Monad.Dep.SimpleAdvice.Advice' whose monad unifies with `DepT e_ m` into an 'Advice'. -fromSimple :: forall ca e_ m r. Monad m => (e_ (DepT e_ m) -> SA.Advice ca (DepT e_ m) r) -> Advice ca e_ m r -fromSimple makeAdvice = Advice \args -> do - env <- ask - case makeAdvice env of - SA.Advice f -> do - let SA.AspectT argsAction = f args - (tweakExecution, args') <- argsAction - pure (coerce tweakExecution, args') - +import Dep.Advice
lib/Control/Monad/Dep/Advice/Basic.hs view
@@ -1,108 +1,7 @@-{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE ExistentialQuantification #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE StandaloneKindSignatures #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE BlockArguments #-} - - --- | --- This module contains basic examples advices. --- --- __/BEWARE!/__ These are provided for illustrative purposes only, they --- strive for simplicity and not robustness or efficiency. -module Control.Monad.Dep.Advice.Basic - ( -- * Basic advices - doLocally +module Control.Monad.Dep.Advice.Basic {-# DEPRECATED "Renamed to Dep.Advice.Basic" #-} + ( + module Dep.Advice.Basic ) where -import Control.Monad.Dep -import Control.Monad.Dep.Advice -import Data.Proxy -import Data.SOP -import Data.SOP (hctraverse_) -import Data.SOP.NP -import Data.Type.Equality -import System.IO -import Type.Reflection -import Control.Concurrent - --- $setup --- --- >>> :set -XTypeApplications --- >>> :set -XStandaloneKindSignatures --- >>> :set -XMultiParamTypeClasses --- >>> :set -XFunctionalDependencies --- >>> :set -XRankNTypes --- >>> :set -XTypeOperators --- >>> :set -XConstraintKinds --- >>> :set -XNamedFieldPuns --- >>> :set -XFlexibleContexts --- >>> :set -XFlexibleInstances --- >>> :set -XAllowAmbiguousTypes --- >>> :set -XBlockArguments --- >>> import Control.Monad --- >>> import Control.Monad.Dep --- >>> import Control.Monad.Dep.Advice --- >>> import Control.Monad.Dep.Advice.Basic --- >>> import Data.Kind --- >>> import Data.SOP --- >>> import Data.SOP.NP --- >>> import Data.Monoid --- >>> import System.IO --- >>> import Data.IORef - - --- | Use 'local' on the final 'DepT' action of a function. --- --- Allows tweaking the environment that will be seen by the function and all of --- its sub-calls into dependencies. --- --- Perhaps this is __not__ what you want; often, it's better to tweak --- the environment for the current function only. For those cases, --- 'Control.Monad.Dep.Advice.deceive' might be a better fit. --- --- >>> :{ --- type HasLogger :: Type -> (Type -> Type) -> Constraint --- class HasLogger em m | em -> m where --- logger :: em -> String -> m () --- type Env :: (Type -> Type) -> Type --- data Env m = Env --- { _logger1 :: String -> m (), --- _logger2 :: String -> m (), --- _controllerA :: Int -> m (), --- _controllerB :: Int -> m () --- } --- instance HasLogger (Env m) m where --- logger = _logger1 --- envIO :: Env (DepT Env IO) --- envIO = Env --- { --- _logger1 = --- \_ -> liftIO $ putStrLn "logger1 ran", --- _logger2 = --- \_ -> liftIO $ putStrLn "logger2 ran", --- _controllerA = --- \_ -> do e <- ask; logger e "foo", --- _controllerB = --- advise @Top --- (doLocally \e@Env{_logger2} -> e {_logger1 = _logger2}) --- \_ -> do e <- ask; logger e "foo" --- } --- :} --- --- >>> runFromEnv (pure envIO) _controllerA 0 --- logger1 ran --- --- >>> runFromEnv (pure envIO) _controllerB 0 --- logger2 ran --- -doLocally :: forall ca e_ m r. Monad m => (e_ (DepT e_ m) -> e_ (DepT e_ m)) -> Advice ca e_ m r -doLocally transform = makeExecutionAdvice (local transform) - - +import Dep.Advice.Basic
lib/Control/Monad/Dep/SimpleAdvice.hs view
@@ -1,495 +1,8 @@-{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE ExistentialQuantification #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE FunctionalDependencies #-} -{-# LANGUAGE GADTSyntax #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE PolyKinds #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE StandaloneKindSignatures #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE UndecidableSuperClasses #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE StandaloneDeriving #-} -{-# LANGUAGE BlockArguments #-} - --- | --- This module provides the 'Advice' datatype, along for functions for creating, --- manipulating, composing and applying values of that type. --- --- 'Advice's are type-preserving transformations on effectful functions of --- any number of arguments. --- --- For example, assuming we have a record-of-functions like --- --- >>> :{ --- data Env m = Env { --- foo :: m () --- , bar :: Int -> m (Maybe Char) --- , baz :: Int -> Bool -> m Char --- } deriving Generic --- env :: Env IO --- env = Env { --- foo = pure () --- , bar = \_ -> pure (Just 'c') --- , baz = \_ _ -> pure 'i' --- } --- :} --- --- We can modify all the functions in the record in this way: --- --- >>> :{ --- env' :: Env IO --- env' = env & advising (adviseRecord @_ @Top \_ -> printArgs stdout "prefix ") --- :} --- --- using the 'Control.Monad.Dep.SimpleAdvice.Basic.printArgs' advice. --- --- Or modify an individual function in this way: --- --- >>> :{ --- env' :: Env IO --- env' = env & advising \env -> env { --- bar = advise (printArgs stdout "prefix ") (bar env) --- } --- :} --- --- __NOTE__: --- --- This module is an alternative to "Control.Monad.Dep.Advice" with two advantages: --- --- - It doesn't use 'Control.Monad.Dep.DepT'. The types are simpler because --- they don't need to refer to 'Control.Monad.Dep.DepT''s environment. --- --- - Unlike in "Control.Monad.Dep.Advice", we can advise components --- which work on a fixed concrete monad like 'IO'. --- --- Compared with "Control.Monad.Dep.Advice", it does require the extra step --- of invoking the 'advising' helper function on a record-of-functions. -module Control.Monad.Dep.SimpleAdvice - ( -- * Preparing components for being advised - advising, - AspectT (..), - -- * The Advice type - Advice, - - -- * Creating Advice values - makeAdvice, - makeArgsAdvice, - makeExecutionAdvice, - - -- * Applying Advices - advise, - - -- * Harmonizing Advice argument constraints - -- $restrict - restrictArgs, - - -- * Advising entire records - -- $records - adviseRecord, - - -- * "sop-core" re-exports - -- $sop - Top, - And, - All, - NP (..), - I (..), - cfoldMap_NP, - Dict (..), +module Control.Monad.Dep.SimpleAdvice {-# DEPRECATED "Renamed to Dep.SimpleAdvice" #-} + ( + module Dep.SimpleAdvice ) where -import Data.Coerce -import Control.Monad -import Control.Monad.Fix -import Control.Monad.Dep.Has -import Data.Functor.Identity -import Data.Kind -import Data.List.NonEmpty qualified as N -import Data.SOP -import Data.SOP.Dict -import Data.SOP.NP -import Data.List.NonEmpty -import Data.Typeable -import GHC.Generics qualified as G -import GHC.TypeLits -import Control.Applicative -import Control.Monad.Cont.Class -import Control.Monad.Error.Class -import Control.Monad.IO.Unlift -import Control.Monad.State.Class -import Control.Monad.Trans.Class -import Control.Monad.Trans.Identity -import Control.Monad.Writer.Class -import Control.Monad.Zip -import Control.Monad.Dep.SimpleAdvice.Internal - --- $setup --- --- >>> :set -XTypeApplications --- >>> :set -XStandaloneKindSignatures --- >>> :set -XMultiParamTypeClasses --- >>> :set -XFunctionalDependencies --- >>> :set -XRankNTypes --- >>> :set -XTypeOperators --- >>> :set -XConstraintKinds --- >>> :set -XNamedFieldPuns --- >>> :set -XFlexibleContexts --- >>> :set -XDerivingStrategies --- >>> :set -XGeneralizedNewtypeDeriving --- >>> :set -XDataKinds --- >>> :set -XScopedTypeVariables --- >>> :set -XDeriveGeneric --- >>> :set -XImportQualifiedPost --- >>> import Control.Monad --- >>> import Control.Monad.Dep --- >>> import Control.Monad.Dep.SimpleAdvice --- >>> import Control.Monad.Dep.SimpleAdvice.Basic (printArgs,returnMempty) --- >>> import Control.Monad.Writer --- >>> import Data.Kind --- >>> import Data.SOP --- >>> import Data.SOP.NP --- >>> import Data.Monoid --- >>> import System.IO --- >>> import Data.IORef --- >>> import GHC.Generics (Generic) --- >>> import GHC.Generics qualified --- >>> import Data.Function - - --- | --- The most general way of constructing 'Advice's. --- --- An 'Advice' receives the arguments of the advised --- function packed into an n-ary product 'NP', performs some --- effects based on them, and returns a potentially modified version of the --- arguments, along with a function for tweaking the execution of the --- advised function. --- --- >>> :{ --- doesNothing :: forall ca m r. Monad m => Advice ca m r --- doesNothing = makeAdvice (\args -> pure (id, args)) --- :} --- --- -makeAdvice :: - forall ca m r. - -- | The function that tweaks the arguments and the execution. - ( forall as. - All ca as => - NP I as -> - AspectT m (AspectT m r -> AspectT m r, NP I as) - ) -> - Advice ca m r -makeAdvice = Advice - --- | --- Create an advice which only tweaks and/or analyzes the function arguments. --- --- >>> :{ --- doesNothing :: forall ca m r. Monad m => Advice ca m r --- doesNothing = makeArgsAdvice pure --- :} -makeArgsAdvice :: - forall ca m r. - Monad m => - -- | The function that tweaks the arguments. - ( forall as. - All ca as => - NP I as -> - AspectT m (NP I as) - ) -> - Advice ca m r -makeArgsAdvice tweakArgs = - makeAdvice $ \args -> do - args' <- tweakArgs args - pure (id, args') - --- | --- Create an advice which only tweaks the execution of the final monadic action. --- --- >>> :{ --- doesNothing :: forall ca m r. Monad m => Advice ca m r --- doesNothing = makeExecutionAdvice id --- :} -makeExecutionAdvice :: - forall ca m r. - Applicative m => - -- | The function that tweaks the execution. - ( AspectT m r -> - AspectT m r - ) -> - Advice ca m r -makeExecutionAdvice tweakExecution = makeAdvice \args -> pure (tweakExecution, args) - - --- | Apply an 'Advice' to some compatible function. The function must have its --- effects in 'AspectT', and all of its arguments must satisfy the @ca@ constraint. --- --- >>> :{ --- foo :: Int -> AspectT IO String --- foo _ = pure "foo" --- advisedFoo = advise (printArgs stdout "Foo args: ") foo --- :} --- --- __/TYPE APPLICATION REQUIRED!/__ If the @ca@ constraint of the 'Advice' remains polymorphic, --- it must be supplied by means of a type application: --- --- >>> :{ --- bar :: Int -> AspectT IO String --- bar _ = pure "bar" --- advisedBar1 = advise (returnMempty @Top) bar --- advisedBar2 = advise @Top returnMempty bar --- :} -advise :: - forall ca m r as advisee. - (Multicurryable as m r advisee, All ca as, Monad m) => - -- | The advice to apply. - Advice ca m r -> - -- | A function to be adviced. - advisee -> - advisee -advise (Advice f) advisee = do - let uncurried = multiuncurry @as @m @r advisee - uncurried' args = do - (tweakExecution, args') <- f args - tweakExecution (uncurried args') - in multicurry @as @m @r uncurried' - --- | This function \"installs\" an 'AspectT' newtype wrapper for the monad --- parameter of a record-of-functions, applies some function on --- the tweaked component, and then removes the wrapper from the result. --- --- This is necessary because the typeclass machinery which handles --- 'Advice's uses 'AspectT' as a \"mark\" to recognize \"the end of the function\". -advising - :: Coercible (r_ m) (r_ (AspectT m)) => - -- | transform the record coerced to 'AspectT', for example using 'adviseRecord' - (r_ (AspectT m) -> r_ (AspectT m)) -> - -- | transform the original record - r_ m -> r_ m -advising f = coerce . f . coerce - -type Multicurryable :: - [Type] -> - (Type -> Type) -> - Type -> - Type -> - Constraint -class Multicurryable as m r curried | curried -> as m r where - multiuncurry :: curried -> NP I as -> AspectT m r - multicurry :: (NP I as -> AspectT m r) -> curried - -instance Monad m => Multicurryable '[] m r (AspectT m r) where - multiuncurry action Nil = action - multicurry f = f Nil - -instance Multicurryable as m r curried => Multicurryable (a ': as) m r (a -> curried) where - multiuncurry f (I a :* as) = multiuncurry @as @m @r @curried (f a) as - multicurry f a = multicurry @as @m @r @curried (f . (:*) (I a)) - --- $restrict --- --- 'Advice' values can be composed using the 'Monoid' instance, but only if --- they have the same type parameters. It's unfortunate that—unlike with --- normal function constraints—the @ca@ constraints of an 'Advice' aren't --- automatically "collected" during composition. --- --- Instead, we need to harmonize the @ca@ constraints of each 'Advice' by --- turning them into the combination of all constraints. 'restrictArgs' --- helps with that. --- --- 'restrictArgs' takes as parameter value-level "\evidence\" that one --- constraint implies another. But how to construct such evidence? By using --- the 'Dict' GADT, more precisely the deceptively simple-looking term --- @\\Dict -> Dict@. That function "absorbs" some constraint present in the --- ambient context and re-packages it a a new constraint that is implied by --- the former. We can't rely on type inference here; we need to provide --- enough type information to the GADT, be it as an explicit signature: --- --- >>> :{ --- stricterPrintArgs :: forall m r. MonadIO m => Advice (Show `And` Eq `And` Ord) m r --- stricterPrintArgs = restrictArgs (\Dict -> Dict) (printArgs stdout "foo") --- :} --- --- or with a type application to 'restrictArgs': --- --- >>> stricterPrintArgs = restrictArgs @(Show `And` Eq `And` Ord) (\Dict -> Dict) (printArgs stdout "foo") - --- | Makes the constraint on the arguments more restrictive. -restrictArgs :: - forall more less m r. - -- | Evidence that one constraint implies the other. Every @x@ that has a @more@ instance also has a @less@ instance. - (forall x. Dict more x -> Dict less x) -> - -- | Advice with less restrictive constraint on the args. - Advice less m r -> - -- | Advice with more restrictive constraint on the args. - Advice more m r --- about the order of the type parameters... which is more useful? --- A possible principle to follow: --- We are likely to know the "less" constraint, because advices are likely to --- come pre-packaged and having a type signature. --- We arent' so sure about having a signature for a whole composed Advice, --- because the composition might be done --- on the fly, while constructing a record, without a top-level binding with a --- type signature. This seems to favor putting "more" first. -restrictArgs evidence (Advice advice) = Advice \args -> - let advice' :: forall as. All more as => NP I as -> AspectT m (AspectT m r -> AspectT m r, NP I as) - advice' args' = - case Data.SOP.Dict.mapAll @more @less evidence of - f -> case f (Dict @(All more) @as) of - Dict -> advice args' - in advice' args - - --- advising *all* fields of a record --- --- -type AdvisedRecord :: (Type -> Constraint) -> (Type -> Type) -> (Type -> Constraint) -> ((Type -> Type) -> Type) -> Constraint -class AdvisedRecord ca m cr advised where - _adviseRecord :: [(TypeRep, String)] -> (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca m r) -> advised (AspectT m) -> advised (AspectT m) - -type AdvisedProduct :: (Type -> Constraint) -> (Type -> Type) -> (Type -> Constraint) -> (k -> Type) -> Constraint -class AdvisedProduct ca m cr advised_ where - _adviseProduct :: TypeRep -> [(TypeRep, String)] -> (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca m r) -> advised_ k -> advised_ k - -instance - ( G.Generic (advised (AspectT m)), - -- G.Rep (advised (AspectT m)) ~ G.D1 ('G.MetaData name mod p nt) (G.C1 y advised_), - G.Rep (advised (AspectT m)) ~ G.D1 x (G.C1 y advised_), - Typeable advised, - AdvisedProduct ca m cr advised_ - ) => - AdvisedRecord ca m cr advised - where - _adviseRecord acc f unadvised = - let G.M1 (G.M1 unadvised_) = G.from unadvised - advised_ = _adviseProduct @_ @ca @m @cr (typeRep (Proxy @advised)) acc f unadvised_ - in G.to (G.M1 (G.M1 advised_)) - -instance - ( AdvisedProduct ca m cr advised_left, - AdvisedProduct ca m cr advised_right - ) => - AdvisedProduct ca m cr (advised_left G.:*: advised_right) - where - _adviseProduct tr acc f (unadvised_left G.:*: unadvised_right) = _adviseProduct @_ @ca @m @cr tr acc f unadvised_left G.:*: _adviseProduct @_ @ca @m @cr tr acc f unadvised_right - -data RecordComponent - = Terminal - | IWrapped - | Recurse - -type DiscriminateAdvisedComponent :: Type -> RecordComponent -type family DiscriminateAdvisedComponent c where - DiscriminateAdvisedComponent (a -> b) = Terminal - DiscriminateAdvisedComponent (AspectT m x) = Terminal - DiscriminateAdvisedComponent (Identity _) = IWrapped - DiscriminateAdvisedComponent (I _) = IWrapped - DiscriminateAdvisedComponent _ = Recurse - -type AdvisedComponent :: RecordComponent -> (Type -> Constraint) -> (Type -> Type) -> (Type -> Constraint) -> Type -> Constraint -class AdvisedComponent component_type ca m cr advised where - _adviseComponent :: [(TypeRep, String)] -> (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca m r) -> advised -> advised - -instance - ( AdvisedComponent (DiscriminateAdvisedComponent advised) ca m cr advised, - KnownSymbol fieldName - ) => - AdvisedProduct ca m cr (G.S1 ( 'G.MetaSel ( 'Just fieldName) su ss ds) (G.Rec0 advised)) - where - _adviseProduct tr acc f (G.M1 (G.K1 advised)) = - let acc' = (tr, symbolVal (Proxy @fieldName)) : acc - in G.M1 (G.K1 (_adviseComponent @(DiscriminateAdvisedComponent advised) @ca @m @cr acc' f advised)) - -instance - AdvisedRecord ca m cr advisable => - AdvisedComponent Recurse ca m cr (advisable (AspectT m)) - where - _adviseComponent acc f advised = _adviseRecord @ca @m @cr acc f advised - -instance - (Multicurryable as m r advised, All ca as, cr r, Monad m) => - AdvisedComponent Terminal ca m cr advised - where - _adviseComponent acc f advised = advise @ca @m (f (Data.List.NonEmpty.fromList acc)) advised - -instance - AdvisedComponent (DiscriminateAdvisedComponent advised) ca m cr advised => - AdvisedComponent IWrapped ca m cr (Identity advised) - where - _adviseComponent acc f (Identity advised) = Identity (_adviseComponent @(DiscriminateAdvisedComponent advised) @ca @m @cr acc f advised) - -instance - AdvisedComponent (DiscriminateAdvisedComponent advised) ca m cr advised => - AdvisedComponent IWrapped ca m cr (I advised) - where - _adviseComponent acc f (I advised) = I (_adviseComponent @(DiscriminateAdvisedComponent advised) @ca @m @cr acc f advised) - --- | Gives 'Advice' to all the functions in a record-of-functions. --- --- The function that builds the advice receives a list of tuples @(TypeRep, String)@ --- which represent the record types and fields names we have --- traversed until arriving at the advised function. This info can be useful for --- logging advices. It's a list instead of a single tuple because --- 'adviseRecord' works recursively. The elements come innermost-first. --- --- __/TYPE APPLICATION REQUIRED!/__ The @ca@ constraint on function arguments --- and the @cr@ constraint on the result type must be supplied by means of a --- type application. Supply 'Top' if no constraint is required. -adviseRecord :: - forall ca cr m advised. - AdvisedRecord ca m cr advised => - -- | The advice to apply. - (forall r . cr r => NonEmpty (TypeRep, String) -> Advice ca m r) -> - -- | The record to advise recursively. - advised (AspectT m) -> - -- | The advised record. - advised (AspectT m) -adviseRecord = _adviseRecord @ca @m @cr [] - --- $sop --- Some useful definitions re-exported the from \"sop-core\" package. --- --- 'NP' is an n-ary product used to represent the arguments of advised functions. --- --- 'I' is an identity functor. The arguments processed by an 'Advice' come wrapped in it. --- --- 'cfoldMap_NP' is useful to construct homogeneous lists out of the 'NP' product, for example: --- --- >>> cfoldMap_NP (Proxy @Show) (\(I a) -> [show a]) (I False :* I (1::Int) :* Nil) --- ["False","1"] - --- $constraints --- --- Some useful definitions re-exported the from \"constraints\" package. --- --- 'Dict' and '(:-)' are GADTs used to capture and transform constraints. Used in the 'restrictArgs' function. - --- $constrainthelpers --- --- To help with the constraint @ca@ that parameterizes 'Advice', this library re-exports the following helpers from \"sop-core\": --- --- * 'Top' is the \"always satisfied\" constraint, useful when whe don't want to require anything specific in @ca@. --- --- * 'And' combines two constraints so that an 'Advice' can request them both, for example @Show \`And\` Eq@. --- --- Also, the 'All' constraint says that some constraint is satisfied by all the --- components of an 'NP' product. It's in scope when processing the function --- arguments inside an 'Advice'. +import Dep.SimpleAdvice --- $invocation --- These functions are helpers for running 'AspectT' computations, beyond what 'runAspectT' provides. --- --- They aren't directly related to 'Advice's, but they require some of the same machinery, and that's why they are here.
lib/Control/Monad/Dep/SimpleAdvice/Basic.hs view
@@ -1,141 +1,7 @@-{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE ExistentialQuantification #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE StandaloneKindSignatures #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE BlockArguments #-} - --- | --- This module contains basic examples advices. --- --- __/BEWARE!/__ These are provided for illustrative purposes only, they --- strive for simplicity and not robustness or efficiency. -module Control.Monad.Dep.SimpleAdvice.Basic - ( -- * Basic advices - returnMempty, - printArgs, - AnyEq (..), - doCachingBadly, - doAsyncBadly +module Control.Monad.Dep.SimpleAdvice.Basic {-# DEPRECATED "Renamed to Dep.SimpleAdvice.Basic" #-} + ( + module Dep.SimpleAdvice.Basic ) where -import Control.Monad.Dep -import Control.Monad.Dep.SimpleAdvice -import Data.Proxy -import Data.SOP -import Data.SOP (hctraverse_) -import Data.SOP.NP -import Data.Type.Equality -import System.IO -import Type.Reflection -import Control.Concurrent -import Control.Monad.IO.Unlift - --- $setup --- --- >>> :set -XTypeApplications --- >>> :set -XStandaloneKindSignatures --- >>> :set -XMultiParamTypeClasses --- >>> :set -XFunctionalDependencies --- >>> :set -XRankNTypes --- >>> :set -XTypeOperators --- >>> :set -XConstraintKinds --- >>> :set -XNamedFieldPuns --- >>> :set -XFlexibleContexts --- >>> :set -XFlexibleInstances --- >>> :set -XAllowAmbiguousTypes --- >>> :set -XBlockArguments --- >>> import Control.Monad --- >>> import Control.Monad.Dep --- >>> import Control.Monad.Dep.SimpleAdvice --- >>> import Control.Monad.Dep.SimpleAdvice.Basic (printArgs,returnMempty) --- >>> import Data.Kind --- >>> import Data.SOP --- >>> import Data.SOP.NP --- >>> import Data.Monoid --- >>> import System.IO --- >>> import Data.IORef - - - --- | Makes functions discard their result and always return 'mempty'. --- -returnMempty :: forall ca m r. (Monad m, Monoid r) => Advice ca m r -returnMempty = - makeExecutionAdvice - ( \action -> do - _ <- action - pure (mempty :: r) - ) - --- | Given a 'Handle' and a prefix string, makes functions print their --- arguments to the 'Handle'. --- -printArgs :: forall m r. MonadIO m => Handle -> String -> Advice Show m r -printArgs h prefix = - makeArgsAdvice - ( \args -> do - liftIO $ hPutStr h $ prefix ++ ":" - hctraverse_ (Proxy @Show) (\(I a) -> liftIO (hPutStr h (" " ++ show a))) args - liftIO $ hPutStrLn h "\n" - liftIO $ hFlush h - pure args - ) - --- | A helper datatype for universal equality comparisons of existentialized values, used by 'doCachingBadly'. --- --- For a more complete elaboration of this idea, see the the \"exinst\" package. -data AnyEq where - AnyEq :: forall a. (Typeable a, Eq a) => a -> AnyEq - -instance Eq AnyEq where - AnyEq any1 == AnyEq any2 = - case testEquality (typeOf any1) (typeOf any2) of - Nothing -> False - Just Refl -> any1 == any2 - --- | --- Given the means for looking up and storing @r@ values in the underlying --- monad @m@, makes functions (inefficiently) cache their results. --- --- The monad @m@ and the result type @r@ must be known before building the --- advice. So, once built, this 'Advice' won't be polymorphic over them. --- --- The implementation of this function makes use of the existential type --- parameter @u@ of 'makeAdvice', because the phase that processes the function --- arguments needs to communicate the calculated `AnyEq` cache key to the phase --- that processes the function result. --- --- A better implementation of this advice would likely use an @AnyHashable@ --- helper datatype for the keys. -doCachingBadly :: forall m r. Monad m => (AnyEq -> m (Maybe r)) -> (AnyEq -> r -> m ()) -> Advice (Eq `And` Typeable) m r -doCachingBadly cacheLookup cachePut = makeAdvice \args -> - let key = AnyEq $ cfoldMap_NP (Proxy @(And Eq Typeable)) (\(I a) -> [AnyEq a]) $ args - tweakExecution action = do - mr <- lift $ cacheLookup key - case mr of - Nothing -> do - r <- action - lift $ cachePut key r - pure r - Just r -> - pure r - in pure (tweakExecution, args) - --- | Makes functions that return `()` launch asynchronously. --- --- A better implementation of this advice would likely use the \"async\" --- package instead of bare `forkIO`. --- --- The @IO@ monad could be generalized to @MonadUnliftIO@. -doAsyncBadly :: forall ca m . MonadUnliftIO m => Advice ca m () -doAsyncBadly = makeExecutionAdvice \action -> do - _ <- withRunInIO (\unlift -> forkIO (unlift action)) - pure () - - +import Dep.SimpleAdvice.Basic
− lib/Control/Monad/Dep/SimpleAdvice/Internal.hs
@@ -1,126 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE ExistentialQuantification #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE FunctionalDependencies #-} -{-# LANGUAGE GADTSyntax #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE PolyKinds #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE StandaloneKindSignatures #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE UndecidableSuperClasses #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE StandaloneDeriving #-} -{-# LANGUAGE BlockArguments #-} - -module Control.Monad.Dep.SimpleAdvice.Internal where - -import Data.Coerce -import Control.Monad -import Control.Monad.Fix -import Control.Monad.Dep.Has -import Data.Functor.Identity -import Data.Kind -import Data.List.NonEmpty qualified as N -import Data.SOP -import Data.SOP.Dict -import Data.SOP.NP -import Data.Typeable -import GHC.Generics qualified as G -import GHC.TypeLits -import Control.Applicative -import Control.Monad.Reader -import Control.Monad.Cont.Class -import Control.Monad.Error.Class -import Control.Monad.IO.Unlift -import Control.Monad.State.Class -import Control.Monad.Trans.Class -import Control.Monad.Trans.Identity -import Control.Monad.Writer.Class -import Control.Monad.Zip - --- | A generic transformation of 'AspectT'-effectful functions with --- base monad @m@ and return type @r@, --- provided the functions satisfy certain constraint @ca@ --- on all of their arguments. --- --- 'Advice's that don't care about the @ca@ constraint (because they don't --- touch function arguments) can leave it polymorphic, and this facilitates --- 'Advice' composition, but then the constraint must be given the catch-all --- `Top` value (using a type application) at the moment of calling 'advise'. --- --- See "Control.Monad.Dep.SimpleAdvice.Basic" for examples. -type Advice :: - (Type -> Constraint) -> - (Type -> Type) -> - Type -> - Type -data Advice (ca :: Type -> Constraint) m r where - Advice :: - forall ca m r. - ( forall as. - All ca as => - NP I as -> - AspectT m (AspectT m r -> AspectT m r, NP I as) - ) -> - Advice ca m r - - --- | --- 'Advice's compose \"sequentially\" when tweaking the arguments, and --- \"concentrically\" when tweaking the final 'AspectT' action. --- --- The first 'Advice' is the \"outer\" one. It tweaks the function arguments --- first, and wraps around the execution of the second, \"inner\" 'Advice'. -instance Monad m => Semigroup (Advice ca m r) where - Advice outer <> Advice inner = Advice \args -> do - (tweakOuter, argsOuter) <- outer args - (tweakInner, argsInner) <- inner argsOuter - pure (tweakOuter . tweakInner, argsInner) - -instance Monad m => Monoid (Advice ca m r) where - mappend = (<>) - mempty = Advice \args -> pure (id, args) - - --- | This transformer is isomorphic to 'Control.Monad.Trans.Identity.IdentityT'. --- --- It doesn't really do anything, it only helps the typeclass machinery. -type AspectT :: - (Type -> Type) -> - Type -> - Type -newtype AspectT (m :: Type -> Type) (r :: Type) = AspectT {runAspectT :: m r} - deriving - ( Functor, - Applicative, - Alternative, - Monad, - MonadFix, - MonadFail, - MonadZip, - MonadPlus, - MonadCont, - MonadIO, - MonadUnliftIO - ) - -instance MonadTrans AspectT where - lift = AspectT - -deriving newtype instance MonadReader env m => MonadReader env (AspectT m) -deriving newtype instance MonadState s m => MonadState s (AspectT m) -deriving newtype instance MonadWriter w m => MonadWriter w (AspectT m) -deriving newtype instance MonadError e m => MonadError e (AspectT m) - -data Pair a b = Pair !a !b -
+ lib/Dep/Advice.hs view
@@ -0,0 +1,972 @@+{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE FunctionalDependencies #-} +{-# LANGUAGE GADTSyntax #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneKindSignatures #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE UndecidableSuperClasses #-} +{-# LANGUAGE BlockArguments #-} + +-- | +-- This module provides the 'Advice' datatype, along for functions for creating, +-- manipulating, composing and applying values of that type. +-- +-- 'Advice's are type-preserving transformations on 'DepT'-effectful functions of +-- any number of arguments. +-- +-- >>> :{ +-- foo0 :: DepT NilEnv IO (Sum Int) +-- foo0 = pure (Sum 5) +-- foo1 :: Bool -> DepT NilEnv IO (Sum Int) +-- foo1 _ = foo0 +-- foo2 :: Char -> Bool -> DepT NilEnv IO (Sum Int) +-- foo2 _ = foo1 +-- :} +-- +-- They work for @DepT@-actions of zero arguments: +-- +-- >>> advise (fromSimple \_ -> printArgs stdout "foo0") foo0 `runDepT` NilEnv +-- foo0: +-- <BLANKLINE> +-- Sum {getSum = 5} +-- +-- And for functions of one or more arguments, provided they end on a @DepT@-action: +-- +-- >>> advise (fromSimple \_ -> printArgs stdout "foo1") foo1 False `runDepT` NilEnv +-- foo1: False +-- <BLANKLINE> +-- Sum {getSum = 5} +-- +-- >>> advise (fromSimple \_ -> printArgs stdout "foo2") foo2 'd' False `runDepT` NilEnv +-- foo2: 'd' False +-- <BLANKLINE> +-- Sum {getSum = 5} +-- +-- 'Advice's can also tweak the result value of functions: +-- +-- >>> advise (fromSimple \_ -> returnMempty @Top) foo2 'd' False `runDepT` NilEnv +-- Sum {getSum = 0} +-- +-- And they can be combined using @Advice@'s 'Monoid' instance before being +-- applied: +-- +-- >>> advise (fromSimple \_ -> printArgs stdout "foo2" <> returnMempty) foo2 'd' False `runDepT` NilEnv +-- foo2: 'd' False +-- <BLANKLINE> +-- Sum {getSum = 0} +-- +-- Although sometimes composition might require harmonizing the constraints +-- each 'Advice' places on the arguments, if they differ. +module Dep.Advice + ( -- * The Advice type + Advice, + + -- * Creating Advice values + makeAdvice, + makeArgsAdvice, + makeExecutionAdvice, + + -- * Applying Advices + advise, + + -- * Constraint helpers + -- $constrainthelpers + Ensure, + + -- * Harmonizing Advice argument constraints + -- $restrict + restrictArgs, + + -- * Invocation helpers + -- $invocation + runFinalDepT, + runFromEnv, + runFromDep, + -- askFinalDepT, + -- * Making functions see a different environment + deceive, + + -- * Advising and deceiving entire records + -- $records + adviseRecord, + deceiveRecord, + -- * Plugging Has-based constructors + component, + --distributeDepT, + + -- * Interfacing with "simple" advices + toSimple, + fromSimple, + fromSimple_, + + -- * "sop-core" re-exports + -- $sop + Top, + And, + All, + NP (..), + I (..), + cfoldMap_NP, + Dict (..), + ) +where + +import Dep.Has +import Dep.Env +import Control.Monad.Dep +import Control.Monad.Trans.Reader (ReaderT (..), withReaderT) +import Data.Functor.Identity +import Data.Kind +import Data.List.NonEmpty qualified as N +import Data.List.NonEmpty (NonEmpty) +import Data.SOP +import Data.SOP.Dict +import Data.SOP.NP +import Data.Typeable +import GHC.Generics qualified as G +import GHC.TypeLits +import Data.Coerce +import Data.Bifunctor (first) +import Dep.SimpleAdvice.Internal qualified as SA + +-- $setup +-- +-- >>> :set -XTypeApplications +-- >>> :set -XStandaloneKindSignatures +-- >>> :set -XMultiParamTypeClasses +-- >>> :set -XFunctionalDependencies +-- >>> :set -XRankNTypes +-- >>> :set -XTypeOperators +-- >>> :set -XConstraintKinds +-- >>> :set -XNamedFieldPuns +-- >>> :set -XFlexibleContexts +-- >>> :set -XDerivingStrategies +-- >>> :set -XGeneralizedNewtypeDeriving +-- >>> :set -XDataKinds +-- >>> :set -XScopedTypeVariables +-- >>> :set -XDeriveGeneric +-- >>> :set -XImportQualifiedPost +-- >>> import Dep.Advice +-- >>> import Dep.SimpleAdvice.Basic (printArgs,returnMempty) +-- >>> import Control.Monad +-- >>> import Control.Monad.Dep +-- >>> import Control.Monad.Writer +-- >>> import Data.Kind +-- >>> import Data.SOP +-- >>> import Data.SOP.NP +-- >>> import Data.Monoid +-- >>> import System.IO +-- >>> import Data.IORef +-- >>> import GHC.Generics (Generic) +-- >>> import GHC.Generics qualified + +-- | A generic transformation of 'DepT'-effectful functions with environment +-- @e_@, base monad @m@ and return type @r@, +-- provided the functions satisfy certain constraint @ca@ +-- on all of their arguments. +-- +-- Note that the type constructor for the environment @e_@ is given unapplied. +-- That is, @Advice Show NilEnv IO ()@ kind-checks but @Advice Show (NilEnv IO) +-- IO ()@ doesn't. See also 'Ensure'. +-- +-- 'Advice's that don't care about the @ca@ constraint (because they don't +-- touch function arguments) can leave it polymorphic, and this facilitates +-- 'Advice' composition, but then the constraint must be given the catch-all +-- `Top` value (using a type application) at the moment of calling 'advise'. +-- +-- See "Control.Monad.Dep.Advice.Basic" for examples. +type Advice :: + (Type -> Constraint) -> + ((Type -> Type) -> Type) -> + (Type -> Type) -> + Type -> + Type +data Advice (ca :: Type -> Constraint) (e_ :: (Type -> Type) -> Type) m r where + Advice :: + forall ca e_ m r. + ( forall as. + All ca as => + NP I as -> + DepT e_ m (DepT e_ m r -> DepT e_ m r, NP I as) + ) -> + Advice ca e_ m r + +-- | +-- 'Advice's compose \"sequentially\" when tweaking the arguments, and +-- \"concentrically\" when tweaking the final 'DepT' action. +-- +-- The first 'Advice' is the \"outer\" one. It tweaks the function arguments +-- first, and wraps around the execution of the second, \"inner\" 'Advice'. +instance Monad m => Semigroup (Advice ca e_ m r) where + Advice outer <> Advice inner = Advice \args -> do + (tweakOuter, argsOuter) <- outer args + (tweakInner, argsInner) <- inner argsOuter + pure (tweakOuter . tweakInner, argsInner) + +instance Monad m => Monoid (Advice ca e_ m r) where + mappend = (<>) + mempty = Advice \args -> pure (id, args) + +-- | +-- The most general way of constructing 'Advice's. +-- +-- An 'Advice' is a function that transforms other functions in an +-- arity-polymorphic way. It receives the arguments of the advised +-- function packed into an n-ary product 'NP', performs some +-- effects based on them, and returns a potentially modified version of the +-- arguments, along with a function for tweaking the execution of the +-- advised function. +-- +-- >>> :{ +-- doesNothing :: forall ca e_ m r. Monad m => Advice ca e_ m r +-- doesNothing = makeAdvice (\args -> pure (id, args)) +-- :} +-- +-- +makeAdvice :: + forall ca e_ m r. + -- | The function that tweaks the arguments and the execution. + ( forall as. + All ca as => + NP I as -> + DepT e_ m (DepT e_ m r -> DepT e_ m r, NP I as) + ) -> + Advice ca e_ m r +makeAdvice = Advice + +-- | +-- Create an advice which only tweaks and/or analyzes the function arguments. +-- +-- >>> :{ +-- doesNothing :: forall ca e_ m r. Monad m => Advice ca e_ m r +-- doesNothing = makeArgsAdvice pure +-- :} +makeArgsAdvice :: + forall ca e_ m r. + Monad m => + -- | The function that tweaks the arguments. + ( forall as. + All ca as => + NP I as -> + DepT e_ m (NP I as) + ) -> + Advice ca e_ m r +makeArgsAdvice tweakArgs = + makeAdvice $ \args -> do + args' <- tweakArgs args + pure (id, args') + +-- | +-- Create an advice which only tweaks the execution of the final monadic action. +-- +-- >>> :{ +-- doesNothing :: forall ca e_ m r. Monad m => Advice ca e_ m r +-- doesNothing = makeExecutionAdvice id +-- :} +makeExecutionAdvice :: + forall ca e_ m r. + Applicative m => + -- | The function that tweaks the execution. + ( DepT e_ m r -> + DepT e_ m r + ) -> + Advice ca e_ m r +makeExecutionAdvice tweakExecution = makeAdvice \args -> pure (tweakExecution, args) + +data Pair a b = Pair !a !b + +-- | +-- 'Ensure' is a helper for lifting typeclass definitions of the form: +-- +-- >>> :{ +-- type HasLogger :: (Type -> Type) -> Type -> Constraint +-- class HasLogger d e | e -> d where +-- logger :: e -> String -> d () +-- :} +-- +-- To work as a constraints on the @e_@ and @m@ parameters of an 'Advice', like this: +-- +-- >>> :{ +-- requiresLogger :: forall e_ m r. (Ensure HasLogger e_ m, Monad m) => Advice Show e_ m r +-- requiresLogger = mempty +-- :} +-- +-- Why is it necessary? Two-place @HasX@-style constraints receive the \"fully +-- applied\" type of the record-of-functions. That is: @NilEnv IO@ instead of +-- simply @NilEnv@. This allows them to also work with monomorphic environments +-- (like those in <http://hackage.haskell.org/package/rio RIO>) whose type +-- isn't parameterized by any monad. +-- +-- But the @e_@ type parameter of 'Advice' has kind @(Type -> Type) -> Type@. +-- That is: @NilEnv@ alone. +-- +-- Furthermore, 'Advices' require @HasX@-style constraints to be placed on the +-- @DepT@ transformer, not directly on the base monad @m@. @Ensure@ takes care +-- of that as well. +type Ensure :: ((Type -> Type) -> Type -> Constraint) -> ((Type -> Type) -> Type) -> (Type -> Type) -> Constraint + +type Ensure c e_ m = c (DepT e_ m) (e_ (DepT e_ m)) + +-- | Apply an 'Advice' to some compatible function. The function must have its +-- effects in 'DepT', and all of its arguments must satisfy the @ca@ constraint. +-- +-- >>> :{ +-- foo :: Int -> DepT NilEnv IO String +-- foo _ = pure "foo" +-- advisedFoo = advise (fromSimple \_ -> printArgs stdout "Foo args: ") foo +-- :} +-- +-- __/TYPE APPLICATION REQUIRED!/__ If the @ca@ constraint of the 'Advice' remains polymorphic, +-- it must be supplied by means of a type application: +-- +-- >>> :{ +-- bar :: Int -> DepT NilEnv IO String +-- bar _ = pure "bar" +-- advisedBar1 = advise (fromSimple \_ -> returnMempty @Top) bar +-- advisedBar2 = advise @Top (fromSimple \_ -> returnMempty) bar +-- :} +advise :: + forall ca e_ m r as advisee. + (Multicurryable as e_ m r advisee, All ca as, Monad m) => + -- | The advice to apply. + Advice ca e_ m r -> + -- | A function to be adviced. + advisee -> + advisee +advise (Advice f) advisee = do + let uncurried = multiuncurry @as @e_ @m @r advisee + uncurried' args = do + (tweakExecution, args') <- f args + tweakExecution (uncurried args') + in multicurry @as @e_ @m @r uncurried' + +type Multicurryable :: + [Type] -> + ((Type -> Type) -> Type) -> + (Type -> Type) -> + Type -> + Type -> + Constraint +class Multicurryable as e_ m r curried | curried -> as e_ m r where + type DownToBaseMonad as e_ m r curried :: Type + multiuncurry :: curried -> NP I as -> DepT e_ m r + multicurry :: (NP I as -> DepT e_ m r) -> curried + _runFromEnv :: m (e_ (DepT e_ m)) -> (e_ (DepT e_ m) -> curried) -> DownToBaseMonad as e_ m r curried + _askFinalDepT :: (e_ (DepT e_ m) -> m curried) -> curried + +instance Monad m => Multicurryable '[] e_ m r (DepT e_ m r) where + type DownToBaseMonad '[] e_ m r (DepT e_ m r) = m r + multiuncurry action Nil = action + multicurry f = f Nil + _runFromEnv producer extractor = do + e <- producer + runDepT (extractor e) e + _askFinalDepT f = do + env <- ask + r <- lift (f env) + r + +instance (Functor m, Multicurryable as e_ m r curried) => Multicurryable (a ': as) e_ m r (a -> curried) where + type DownToBaseMonad (a ': as) e_ m r (a -> curried) = a -> DownToBaseMonad as e_ m r curried + multiuncurry f (I a :* as) = multiuncurry @as @e_ @m @r @curried (f a) as + multicurry f a = multicurry @as @e_ @m @r @curried (f . (:*) (I a)) + _runFromEnv producer extractor a = _runFromEnv @as @e_ @m @r @curried producer (\f -> extractor f a) + _askFinalDepT f = + let switcheroo action a = fmap ($ a) action + in _askFinalDepT @as @e_ @m @r . flip (fmap switcheroo f) + +-- | Given a base monad @m@ action that gets hold of the 'DepT' environment, run +-- the 'DepT' transformer at the tip of a curried function. +-- +-- >>> :{ +-- foo :: Int -> Int -> Int -> DepT NilEnv IO () +-- foo _ _ _ = pure () +-- :} +-- +-- >>> runFinalDepT (pure NilEnv) foo 1 2 3 :: IO () +runFinalDepT :: + forall as e_ m r curried. + Multicurryable as e_ m r curried => + -- | action that gets hold of the environment + m (e_ (DepT e_ m)) -> + -- | function to invoke with effects in 'DepT' + curried -> + -- | a new function with effects in the base monad + DownToBaseMonad as e_ m r curried +runFinalDepT producer extractor = _runFromEnv producer (const extractor) + +askFinalDepT :: + forall as e_ m r curried. + Multicurryable as e_ m r curried => + (e_ (DepT e_ m) -> m curried) -> curried +askFinalDepT = _askFinalDepT @as @e_ @m @r + +-- | Given a base monad @m@ action that gets hold of the 'DepT' environment, +-- and a function capable of extracting a curried function from the +-- environment, run the 'DepT' transformer at the tip of the resulting curried +-- function. +-- +-- Why put the environment behind the @m@ action? Well, since getting to the +-- end of the curried function takes some work, it's a good idea to have some +-- flexibility once we arrive there. For example, the environment could be +-- stored in a "Data.IORef" and change in response to events, perhaps with +-- advices being added or removed. +-- +-- >>> :{ +-- type MutableEnv :: (Type -> Type) -> Type +-- data MutableEnv m = MutableEnv { _foo :: Int -> m (Sum Int) } +-- :} +-- +-- >>> :{ +-- do envRef <- newIORef (MutableEnv (pure . Sum)) +-- let foo' = runFromEnv (readIORef envRef) _foo +-- do r <- foo' 7 +-- print r +-- modifyIORef envRef (\e -> e { _foo = advise @Top (fromSimple \_ -> returnMempty) (_foo e) }) +-- do r <- foo' 7 +-- print r +-- :} +-- Sum {getSum = 7} +-- Sum {getSum = 0} +runFromEnv :: + forall as e_ m r curried. + Multicurryable as e_ m r curried => + -- | action that gets hold of the environment + m (e_ (DepT e_ m)) -> + -- | gets a function from the environment with effects in 'DepT' + (e_ (DepT e_ m) -> curried) -> + -- | a new function with effects in the base monad + DownToBaseMonad as e_ m r curried +runFromEnv = _runFromEnv + +-- | Like 'runFromEnv', but the function to run is extracted from a dependency +-- @dep@ which is found using 'Has'. The selector should be concrete enough to +-- identify @dep@ in the environment. +runFromDep :: + forall dep as e_ m r curried. + (Multicurryable as e_ m r curried, Has dep (DepT e_ m) (e_ (DepT e_ m))) => + -- | action that gets hold of the environment + m (e_ (DepT e_ m)) -> + -- | selector that gets a function from a dependency found using 'Has' + (dep (DepT e_ m) -> curried) -> + -- | a new function with effects in the base monad + DownToBaseMonad as e_ m r curried +runFromDep envAction member = _runFromEnv envAction (member . dep) + +-- $restrict +-- +-- 'Advice' values can be composed using the 'Monoid' instance, but only if +-- they have the same type parameters. It's unfortunate that—unlike with +-- normal function constraints—the @ca@ constraints of an 'Advice' aren't +-- automatically "collected" during composition. +-- +-- Instead, we need to harmonize the @ca@ constraints of each 'Advice' by +-- turning them into the combination of all constraints. 'restrictArgs' +-- helps with that. +-- +-- 'restrictArgs' takes as parameter value-level "\evidence\" that one +-- constraint implies another. But how to construct such evidence? By using +-- the 'Dict' GADT, more precisely the deceptively simple-looking term +-- @\\Dict -> Dict@. That function "absorbs" some constraint present in the +-- ambient context and re-packages it a a new constraint that is implied by +-- the former. We can't rely on type inference here; we need to provide +-- enough type information to the GADT, be it as an explicit signature: +-- +-- >>> :{ +-- stricterPrintArgs :: forall e_ m r. MonadIO m => Advice (Show `And` Eq `And` Ord) e_ m r +-- stricterPrintArgs = restrictArgs (\Dict -> Dict) (fromSimple \_ -> printArgs stdout "foo") +-- :} +-- +-- or with a type application to 'restrictArgs': +-- +-- >>> stricterPrintArgs = restrictArgs @(Show `And` Eq `And` Ord) (\Dict -> Dict) (fromSimple \_ -> printArgs stdout "foo") + +-- | Makes the constraint on the arguments more restrictive. +restrictArgs :: + forall more less e_ m r. + -- | Evidence that one constraint implies the other. Every @x@ that has a @more@ instance also has a @less@ instance. + (forall x. Dict more x -> Dict less x) -> + -- | Advice with less restrictive constraint on the args. + Advice less e_ m r -> + -- | Advice with more restrictive constraint on the args. + Advice more e_ m r +-- about the order of the type parameters... which is more useful? +-- A possible principle to follow: +-- We are likely to know the "less" constraint, because advices are likely to +-- come pre-packaged and having a type signature. +-- We arent' so sure about having a signature for a whole composed Advice, +-- because the composition might be done +-- on the fly, while constructing a record, without a top-level binding with a +-- type signature. This seems to favor putting "more" first. +restrictArgs evidence (Advice advice) = Advice \args -> + let advice' :: forall as. All more as => NP I as -> DepT e_ m (DepT e_ m r -> DepT e_ m r, NP I as) + advice' args' = + case Data.SOP.Dict.mapAll @more @less evidence of + f -> case f (Dict @(All more) @as) of + Dict -> advice args' + in advice' args + +-- +type Gullible :: + [Type] -> + Type -> + ((Type -> Type) -> Type) -> + (Type -> Type) -> + Type -> + Type -> + Constraint +class Multicurryable as e_ m r curried => Gullible as e e_ m r curried where + type NewtypedEnv as e e_ m r curried :: Type + _deceive :: (e_ (DepT e_ m) -> e) -> NewtypedEnv as e e_ m r curried -> curried + +instance Monad m => Gullible '[] e e_ m r (DepT e_ m r) where + type NewtypedEnv '[] e e_ m r (DepT e_ m r) = ReaderT e m r + _deceive f action = DepT (withReaderT f action) + +instance (Functor m, Gullible as e e_ m r curried) => Gullible (a ': as) e e_ m r (a -> curried) where + type NewtypedEnv (a ': as) e e_ m r (a -> curried) = a -> NewtypedEnv as e e_ m r curried + _deceive f g a = deceive @as @e @e_ @m @r f (g a) + +-- | Makes a function see a newtyped version of the environment record, a version that might have different @HasX@ instances. +-- +-- The \"deception\" doesn't affect the dependencies used by the function, only the function itself. +-- +-- For example, consider the following setup which features both a \"deceviced\" +-- and an \"undeceived\" version of a controller function: +-- +-- >>> :{ +-- type HasLogger :: (Type -> Type) -> Type -> Constraint +-- class HasLogger d e | e -> d where +-- logger :: e -> String -> d () +-- type HasIntermediate :: (Type -> Type) -> Type -> Constraint +-- class HasIntermediate d e | e -> d where +-- intermediate :: e -> String -> d () +-- type Env :: (Type -> Type) -> Type +-- data Env m = Env +-- { _logger1 :: String -> m (), +-- _logger2 :: String -> m (), +-- _intermediate :: String -> m (), +-- _controllerA :: Int -> m (), +-- _controllerB :: Int -> m () +-- } +-- instance HasLogger m (Env m) where +-- logger = _logger1 +-- instance HasIntermediate m (Env m) where +-- intermediate = _intermediate +-- newtype Switcheroo m = Switcheroo (Env m) +-- deriving newtype (HasIntermediate m) +-- instance HasLogger m (Switcheroo m) where +-- logger (Switcheroo e) = _logger2 e +-- env :: Env (DepT Env (Writer [String])) +-- env = +-- let mkController :: forall d e m. MonadDep [HasLogger, HasIntermediate] d e m => Int -> m () +-- mkController _ = do e <- ask; liftD $ logger e "foo" ; liftD $ intermediate e "foo" +-- mkIntermediate :: forall d e m. MonadDep '[HasLogger] d e m => String -> m () +-- mkIntermediate _ = do e <- ask ; liftD $ logger e "foo" +-- in Env +-- { +-- _logger1 = +-- \_ -> tell ["logger 1"], +-- _logger2 = +-- \_ -> tell ["logger 2"], +-- _intermediate = +-- mkIntermediate, +-- _controllerA = +-- mkController, +-- _controllerB = +-- deceive Switcheroo $ +-- mkController +-- } +-- :} +-- +-- If we run @_controllerA@ the ouput is: +-- +-- >>> execWriter $ runFromEnv (pure env) _controllerA 7 +-- ["logger 1","logger 1"] +-- +-- But if we run the \"deceived\" @_controllerB@, we see that the function and its @_intermediate@ dependency use different loggers: +-- +-- >>> execWriter $ runFromEnv (pure env) _controllerB 7 +-- ["logger 2","logger 1"] +-- +-- Note that the function that is \"deceived\" must be polymorphic over +-- 'Control.Monad.Dep.MonadDep'. Passing a function whose effect monad has +-- already \"collapsed\" into 'DepT' won't work. Therefore, 'deceive' must be applied before any 'Advice'. +deceive :: + forall as e e_ m r curried. + Gullible as e e_ m r curried => + -- | The newtype constructor that masks the \"true\" environment. + (e_ (DepT e_ m) -> e) -> + -- | A function to be deceived. It must be polymorphic over 'Control.Monad.Dep.MonadDep'. + NewtypedEnv as e e_ m r curried -> + -- | The deceived function, that has effects in 'DepT'. + curried +deceive = _deceive + +-- deceving *all* fields of a record +-- +-- +type GullibleRecord :: Type -> ((Type -> Type) -> Type) -> (Type -> Type) -> ((Type -> Type) -> Type) -> Constraint +class GullibleRecord e e_ m gullible where + _deceiveRecord :: (e_ (DepT e_ m) -> e) -> gullible (ReaderT e m) -> gullible (DepT e_ m) + +-- https://gitlab.haskell.org/ghc/ghc/-/issues/13952 +type GullibleProduct :: Type -> ((Type -> Type) -> Type) -> (Type -> Type) -> (k -> Type) -> (k -> Type) -> Constraint +class GullibleProduct e e_ m gullible_ deceived_ | e e_ m deceived_ -> gullible_ where + _deceiveProduct :: (e_ (DepT e_ m) -> e) -> gullible_ k -> deceived_ k + +instance + ( GullibleProduct e e_ m gullible_left deceived_left, + GullibleProduct e e_ m gullible_right deceived_right + ) => + GullibleProduct e e_ m (gullible_left G.:*: gullible_right) (deceived_left G.:*: deceived_right) + where + _deceiveProduct f (gullible_left G.:*: gullible_right) = _deceiveProduct @_ @e @e_ @m f gullible_left G.:*: _deceiveProduct @_ @e @e_ @m f gullible_right + +data RecordComponent + = Terminal + | IWrapped + | Recurse + +type DiscriminateGullibleComponent :: Type -> RecordComponent +type family DiscriminateGullibleComponent c where + DiscriminateGullibleComponent (a -> b) = Terminal + DiscriminateGullibleComponent (ReaderT e m x) = Terminal + DiscriminateGullibleComponent (Identity _) = IWrapped + DiscriminateGullibleComponent (I _) = IWrapped + DiscriminateGullibleComponent _ = Recurse + +type GullibleComponent :: RecordComponent -> Type -> ((Type -> Type) -> Type) -> (Type -> Type) -> Type -> Type -> Constraint +class GullibleComponent component_type e e_ m gullible deceived | e e_ m deceived -> gullible where + _deceiveComponent :: (e_ (DepT e_ m) -> e) -> gullible -> deceived + +instance + (Gullible as e e_ m r deceived, NewtypedEnv as e e_ m r deceived ~ gullible) => + GullibleComponent Terminal e e_ m gullible deceived + where + _deceiveComponent f gullible = deceive @as @e @_ @m @r f gullible + +instance + GullibleComponent (DiscriminateGullibleComponent gullible) e e_ m gullible deceived => + GullibleComponent IWrapped e e_ m (Identity gullible) (Identity deceived) + where + _deceiveComponent f (Identity gullible) = Identity (_deceiveComponent @(DiscriminateGullibleComponent gullible) @e @e_ @m f gullible) + +instance + GullibleComponent (DiscriminateGullibleComponent gullible) e e_ m gullible deceived => + GullibleComponent IWrapped e e_ m (I gullible) (I deceived) + where + _deceiveComponent f (I gullible) = I (_deceiveComponent @(DiscriminateGullibleComponent gullible) @e @e_ @m f gullible) + +instance + GullibleRecord e e_ m gullible => + GullibleComponent Recurse e e_ m (gullible (ReaderT e m)) (gullible (DepT e_ m)) + where + _deceiveComponent f gullible = _deceiveRecord @e @e_ @m f gullible + +instance + GullibleComponent (DiscriminateGullibleComponent gullible) e e_ m gullible deceived => + GullibleProduct e e_ m (G.S1 x (G.Rec0 gullible)) (G.S1 x (G.Rec0 deceived)) + where + _deceiveProduct f (G.M1 (G.K1 gullible)) = G.M1 (G.K1 (_deceiveComponent @(DiscriminateGullibleComponent gullible) @e @e_ @m f gullible)) + +instance + ( G.Generic (gullible (ReaderT e m)), + G.Generic (gullible (DepT e_ m)), + G.Rep (gullible (ReaderT e m)) ~ G.D1 x (G.C1 y gullible_), + G.Rep (gullible (DepT e_ m)) ~ G.D1 x (G.C1 y deceived_), + GullibleProduct e e_ m gullible_ deceived_ + ) => + GullibleRecord e e_ m gullible + where + _deceiveRecord f gullible = + let G.M1 (G.M1 gullible_) = G.from gullible + deceived_ = _deceiveProduct @_ @e @e_ @m f gullible_ + in G.to (G.M1 (G.M1 deceived_)) + +-- | Makes an entire record-of-functions see a different version of the global environment record, a version that might have different @HasX@ instances. +-- +-- 'deceiveRecord' must be applied before 'adviseRecord'. +deceiveRecord :: + forall e e_ m gullible. + GullibleRecord e e_ m gullible => + -- | The newtype constructor that masks the \"true\" environment. + (e_ (DepT e_ m) -> e) -> + -- | The record to deceive recursively. The monad parameter must be left polymorphic over @MonadDep@, so that it can unify with `ReaderT`. + gullible (ReaderT e m) -> + -- | The deceived record. + gullible (DepT e_ m) +deceiveRecord = _deceiveRecord @e @e_ @m @gullible + + +-- | Given a constructor that returns a record-of-functions with effects in 'DepT', +-- produce a record in which the member functions 'ask' for the environment themselves. +-- +-- You must have a sufficiently polymorphic constructor—both in the monad and +-- in the environment—to invoke this function. +-- +-- 'component' lets you plug simple component constructors +-- into a 'DepT'-based environment. +-- +-- Compare with 'Control.Monad.Dep.Env.constructor' from "Control.Monad.Dep.Env", which +-- is intended to be used with 'Control.Monad.Dep.Env.fixEnv'-based environments. +component + :: forall e_ m record . (Applicative m, DistributiveRecord e_ m record) => + -- | constructor which takes the environment as a positional parameter. + (e_ (DepT e_ m) -> record (DepT e_ m)) -> + -- | component whose methods get the environment by 'ask'ing. + record (DepT e_ m) +component f = _distribute @e_ @m (pure . f) + + + +type DistributiveRecord :: ((Type -> Type) -> Type) -> (Type -> Type) -> ((Type -> Type) -> Type) -> Constraint +class DistributiveRecord e_ m record where + _distribute :: (e_ (DepT e_ m) -> m (record (DepT e_ m))) -> record (DepT e_ m) + +type DistributiveProduct :: ((Type -> Type) -> Type) -> (Type -> Type) -> (k -> Type) -> Constraint +class DistributiveProduct e_ m product where + _distributeProduct :: (e_ (DepT e_ m) -> m (product k)) -> product k + +instance + ( G.Generic (advised (DepT e_ m)), + G.Rep (advised (DepT e_ m)) ~ G.D1 x (G.C1 y advised_), + DistributiveProduct e_ m advised_, + Functor m + ) => + DistributiveRecord e_ m advised + where + _distribute f = + let advised_ = _distributeProduct @_ @e_ @m (fmap (fmap (G.unM1 . G.unM1 . G.from)) f) + in G.to (G.M1 (G.M1 advised_)) + +instance + ( DistributiveProduct e_ m advised_left, + DistributiveProduct e_ m advised_right, + Functor m + ) => + DistributiveProduct e_ m (advised_left G.:*: advised_right) + where + _distributeProduct f = + _distributeProduct @_ @e_ @m (fmap (fmap (\(l G.:*: _) -> l)) f) + G.:*: + _distributeProduct @_ @e_ @m (fmap (fmap (\(_ G.:*: r) -> r)) f) + +instance + ( + Functor m, + Multicurryable as e_ m r advised + ) => + DistributiveProduct e_ m (G.S1 ( 'G.MetaSel msymbol su ss ds) (G.Rec0 advised)) + where + _distributeProduct f = G.M1 . G.K1 $ askFinalDepT @as @e_ @m @r (fmap (fmap (G.unK1 . G.unM1)) f) + +-- advising *all* fields of a record +-- +-- +type AdvisedRecord :: (Type -> Constraint) -> ((Type -> Type) -> Type) -> (Type -> Type) -> (Type -> Constraint) -> ((Type -> Type) -> Type) -> Constraint +class AdvisedRecord ca e_ m cr advised where + _adviseRecord :: [(TypeRep, String)] -> (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca e_ m r) -> advised (DepT e_ m) -> advised (DepT e_ m) + +type AdvisedProduct :: (Type -> Constraint) -> ((Type -> Type) -> Type) -> (Type -> Type) -> (Type -> Constraint) -> (k -> Type) -> Constraint +class AdvisedProduct ca e_ m cr advised_ where + _adviseProduct :: TypeRep -> [(TypeRep, String)] -> (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca e_ m r) -> advised_ k -> advised_ k + +instance + ( G.Generic (advised (DepT e_ m)), + -- G.Rep (advised (DepT e_ m)) ~ G.D1 ('G.MetaData name mod p nt) (G.C1 y advised_), + G.Rep (advised (DepT e_ m)) ~ G.D1 x (G.C1 y advised_), + Typeable advised, + AdvisedProduct ca e_ m cr advised_ + ) => + AdvisedRecord ca e_ m cr advised + where + _adviseRecord acc f unadvised = + let G.M1 (G.M1 unadvised_) = G.from unadvised + advised_ = _adviseProduct @_ @ca @e_ @m @cr (typeRep (Proxy @advised)) acc f unadvised_ + in G.to (G.M1 (G.M1 advised_)) + +instance + ( AdvisedProduct ca e_ m cr advised_left, + AdvisedProduct ca e_ m cr advised_right + ) => + AdvisedProduct ca e_ m cr (advised_left G.:*: advised_right) + where + _adviseProduct tr acc f (unadvised_left G.:*: unadvised_right) = _adviseProduct @_ @ca @e_ @m @cr tr acc f unadvised_left G.:*: _adviseProduct @_ @ca @e_ @m @cr tr acc f unadvised_right + +type DiscriminateAdvisedComponent :: Type -> RecordComponent +type family DiscriminateAdvisedComponent c where + DiscriminateAdvisedComponent (a -> b) = Terminal + DiscriminateAdvisedComponent (DepT e_ m x) = Terminal + DiscriminateAdvisedComponent (Identity _) = IWrapped + DiscriminateAdvisedComponent (I _) = IWrapped + DiscriminateAdvisedComponent _ = Recurse + +type AdvisedComponent :: RecordComponent -> (Type -> Constraint) -> ((Type -> Type) -> Type) -> (Type -> Type) -> (Type -> Constraint) -> Type -> Constraint +class AdvisedComponent component_type ca e_ m cr advised where + _adviseComponent :: [(TypeRep, String)] -> (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca e_ m r) -> advised -> advised + +instance + ( AdvisedComponent (DiscriminateAdvisedComponent advised) ca e_ m cr advised, + KnownSymbol fieldName + ) => + AdvisedProduct ca e_ m cr (G.S1 ( 'G.MetaSel ( 'Just fieldName) su ss ds) (G.Rec0 advised)) + where + _adviseProduct tr acc f (G.M1 (G.K1 advised)) = + let acc' = (tr, symbolVal (Proxy @fieldName)) : acc + in G.M1 (G.K1 (_adviseComponent @(DiscriminateAdvisedComponent advised) @ca @e_ @m @cr acc' f advised)) + +instance + AdvisedRecord ca e_ m cr advisable => + AdvisedComponent Recurse ca e_ m cr (advisable (DepT e_ m)) + where + _adviseComponent acc f advised = _adviseRecord @ca @e_ @m @cr acc f advised + +instance + (Multicurryable as e_ m r advised, All ca as, cr r, Monad m) => + AdvisedComponent Terminal ca e_ m cr advised + where + _adviseComponent acc f advised = advise @ca @e_ @m (f (N.fromList acc)) advised + +instance + AdvisedComponent (DiscriminateAdvisedComponent advised) ca e_ m cr advised => + AdvisedComponent IWrapped ca e_ m cr (Identity advised) + where + _adviseComponent acc f (Identity advised) = Identity (_adviseComponent @(DiscriminateAdvisedComponent advised) @ca @e_ @m @cr acc f advised) + +instance + AdvisedComponent (DiscriminateAdvisedComponent advised) ca e_ m cr advised => + AdvisedComponent IWrapped ca e_ m cr (I advised) + where + _adviseComponent acc f (I advised) = I (_adviseComponent @(DiscriminateAdvisedComponent advised) @ca @e_ @m @cr acc f advised) + +-- | Gives 'Advice' to all the functions in a record-of-functions. +-- +-- The function that builds the advice receives a list of tuples @(TypeRep, String)@ +-- which represent the record types and fields names we have +-- traversed until arriving at the advised function. This info can be useful for +-- logging advices. It's a list instead of a single tuple because +-- 'adviseRecord' works recursively. The elements come innermost-first. +-- +-- __/TYPE APPLICATION REQUIRED!/__ The @ca@ constraint on function arguments +-- and the @cr@ constraint on the result type must be supplied by means of a +-- type application. Supply 'Top' if no constraint is required. +adviseRecord :: + forall ca cr e_ m advised. + AdvisedRecord ca e_ m cr advised => + -- | The advice to apply. + (forall r . cr r => NonEmpty (TypeRep, String) -> Advice ca e_ m r) -> + -- | The record to advise recursively. + advised (DepT e_ m) -> + -- | The advised record. + advised (DepT e_ m) +adviseRecord = _adviseRecord @ca @e_ @m @cr [] + +-- $records +-- +-- 'adviseRecord' and 'deceiveRecord' are versions of 'advise' and 'deceive' that, instead of working on bare +-- functions, transform entire records-of-functions in one go. They also work +-- with newtypes containing a single function. The records must derive 'GHC.Generics.Generic'. +-- +-- Useful with the \"wrapped\" style of components facilitated by @Control.Monad.Dep.Has@. +-- +-- >>> :{ +-- type Logger :: (Type -> Type) -> Type +-- newtype Logger d = Logger {log :: String -> d ()} deriving Generic +-- type Repository :: (Type -> Type) -> Type +-- data Repository d = Repository +-- { select :: String -> d [Int], +-- insert :: [Int] -> d () +-- } deriving Generic +-- type Controller :: (Type -> Type) -> Type +-- newtype Controller d = Controller {serve :: Int -> d String} deriving Generic +-- type Env :: (Type -> Type) -> Type +-- data Env m = Env +-- { logger :: Logger m, +-- repository :: Repository m, +-- controller :: Controller m +-- } +-- newtype Wraps x = Wraps x +-- env :: Env (DepT Env (Writer ())) +-- env = +-- let logger = Logger \_ -> pure () +-- repository = +-- adviseRecord @Top @Top mempty $ +-- deceiveRecord Wraps $ +-- Repository {select = \_ -> pure [], insert = \_ -> pure ()} +-- controller = +-- adviseRecord @Top @Top mempty $ +-- deceiveRecord Wraps $ +-- Controller \_ -> pure "view" +-- in Env {logger, repository, controller} +-- :} + +-- $sop +-- Some useful definitions re-exported the from \"sop-core\" package. +-- +-- 'NP' is an n-ary product used to represent the arguments of advised functions. +-- +-- 'I' is an identity functor. The arguments processed by an 'Advice' come wrapped in it. +-- +-- 'cfoldMap_NP' is useful to construct homogeneous lists out of the 'NP' product, for example: +-- +-- >>> cfoldMap_NP (Proxy @Show) (\(I a) -> [show a]) (I False :* I (1::Int) :* Nil) +-- ["False","1"] + +-- $constraints +-- +-- Some useful definitions re-exported the from \"constraints\" package. +-- +-- 'Dict' and '(:-)' are GADTs used to capture and transform constraints. Used in the 'restrictArgs' function. + +-- $constrainthelpers +-- +-- To help with the constraint @ca@ that parameterizes 'Advice', this library re-exports the following helpers from \"sop-core\": +-- +-- * 'Top' is the \"always satisfied\" constraint, useful when whe don't want to require anything specific in @ca@. +-- +-- * 'And' combines two constraints so that an 'Advice' can request them both, for example @Show \`And\` Eq@. +-- +-- Also, the 'All' constraint says that some constraint is satisfied by all the +-- components of an 'NP' product. It's in scope when processing the function +-- arguments inside an 'Advice'. + +-- $invocation +-- These functions are helpers for running 'DepT' computations, beyond what 'runDepT' provides. +-- +-- They aren't directly related to 'Advice's, but they require some of the same machinery, and that's why they are here. + +-- | An advice that is polymorphic on the environment (allowing it to unify +-- with 'Control.Monad.Dep.NilEnv') can be converted to a "simple" 'Control.Monad.Dep.SimpleAdvice.Advice' that doesn't require 'Control.Monad.Dep.DepT' at all. +toSimple :: Monad m => Advice ca NilEnv m r -> SA.Advice ca m r +toSimple (Advice f) = SA.Advice \args -> lift do + (withExecution, args') <- f args `runDepT` NilEnv + let withExecution' = lift . flip runDepT NilEnv . withExecution . lift . SA.runAspectT + pure (withExecution', args') + +-- | Convert a simple 'Control.Monad.Dep.SimpleAdvice.Advice' whose monad unifies with `DepT e_ m` into an 'Advice'. +fromSimple :: forall ca e_ m r. Monad m => (e_ (DepT e_ m) -> SA.Advice ca (DepT e_ m) r) -> Advice ca e_ m r +fromSimple makeAdvice = Advice \args -> do + env <- ask + case makeAdvice env of + SA.Advice f -> do + let SA.AspectT argsAction = f args + (tweakExecution, args') <- argsAction + pure (coerce tweakExecution, args') + +-- | Like 'fromSimple', but for 'Control.Monad.Dep.SimpleAdvice.Advice's that don't use the environment. +fromSimple_ :: forall ca e_ m r. Monad m => SA.Advice ca (DepT e_ m) r -> Advice ca e_ m r +fromSimple_ advice = fromSimple \_ -> advice +
+ lib/Dep/Advice/Basic.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneKindSignatures #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE BlockArguments #-} + + +-- | +-- This module contains basic examples advices. +-- +-- __/BEWARE!/__ These are provided for illustrative purposes only, they +-- strive for simplicity and not robustness or efficiency. +module Dep.Advice.Basic + ( -- * Basic advices + doLocally + ) +where + +import Dep.Advice +import Control.Monad.Dep +import Data.Proxy +import Data.SOP +import Data.SOP (hctraverse_) +import Data.SOP.NP +import Data.Type.Equality +import System.IO +import Type.Reflection +import Control.Concurrent + +-- $setup +-- +-- >>> :set -XTypeApplications +-- >>> :set -XStandaloneKindSignatures +-- >>> :set -XMultiParamTypeClasses +-- >>> :set -XFunctionalDependencies +-- >>> :set -XRankNTypes +-- >>> :set -XTypeOperators +-- >>> :set -XConstraintKinds +-- >>> :set -XNamedFieldPuns +-- >>> :set -XFlexibleContexts +-- >>> :set -XFlexibleInstances +-- >>> :set -XAllowAmbiguousTypes +-- >>> :set -XBlockArguments +-- >>> import Dep.Advice +-- >>> import Dep.Advice.Basic +-- >>> import Control.Monad +-- >>> import Control.Monad.Dep +-- >>> import Data.Kind +-- >>> import Data.SOP +-- >>> import Data.SOP.NP +-- >>> import Data.Monoid +-- >>> import System.IO +-- >>> import Data.IORef + + +-- | Use 'local' on the final 'DepT' action of a function. +-- +-- Allows tweaking the environment that will be seen by the function and all of +-- its sub-calls into dependencies. +-- +-- Perhaps this is __not__ what you want; often, it's better to tweak +-- the environment for the current function only. For those cases, +-- 'Control.Monad.Dep.Advice.deceive' might be a better fit. +-- +-- >>> :{ +-- type HasLogger :: Type -> (Type -> Type) -> Constraint +-- class HasLogger em m | em -> m where +-- logger :: em -> String -> m () +-- type Env :: (Type -> Type) -> Type +-- data Env m = Env +-- { _logger1 :: String -> m (), +-- _logger2 :: String -> m (), +-- _controllerA :: Int -> m (), +-- _controllerB :: Int -> m () +-- } +-- instance HasLogger (Env m) m where +-- logger = _logger1 +-- envIO :: Env (DepT Env IO) +-- envIO = Env +-- { +-- _logger1 = +-- \_ -> liftIO $ putStrLn "logger1 ran", +-- _logger2 = +-- \_ -> liftIO $ putStrLn "logger2 ran", +-- _controllerA = +-- \_ -> do e <- ask; logger e "foo", +-- _controllerB = +-- advise @Top +-- (doLocally \e@Env{_logger2} -> e {_logger1 = _logger2}) +-- \_ -> do e <- ask; logger e "foo" +-- } +-- :} +-- +-- >>> runFromEnv (pure envIO) _controllerA 0 +-- logger1 ran +-- +-- >>> runFromEnv (pure envIO) _controllerB 0 +-- logger2 ran +-- +doLocally :: forall ca e_ m r. Monad m => (e_ (DepT e_ m) -> e_ (DepT e_ m)) -> Advice ca e_ m r +doLocally transform = makeExecutionAdvice (local transform) + +
+ lib/Dep/SimpleAdvice.hs view
@@ -0,0 +1,495 @@+{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE FunctionalDependencies #-} +{-# LANGUAGE GADTSyntax #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneKindSignatures #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE UndecidableSuperClasses #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE BlockArguments #-} + +-- | +-- This module provides the 'Advice' datatype, along for functions for creating, +-- manipulating, composing and applying values of that type. +-- +-- 'Advice's are type-preserving transformations on effectful functions of +-- any number of arguments. +-- +-- For example, assuming we have a record-of-functions like +-- +-- >>> :{ +-- data Env m = Env { +-- foo :: m () +-- , bar :: Int -> m (Maybe Char) +-- , baz :: Int -> Bool -> m Char +-- } deriving Generic +-- env :: Env IO +-- env = Env { +-- foo = pure () +-- , bar = \_ -> pure (Just 'c') +-- , baz = \_ _ -> pure 'i' +-- } +-- :} +-- +-- We can modify all the functions in the record in this way: +-- +-- >>> :{ +-- env' :: Env IO +-- env' = env & advising (adviseRecord @_ @Top \_ -> printArgs stdout "prefix ") +-- :} +-- +-- using the 'Control.Monad.Dep.SimpleAdvice.Basic.printArgs' advice. +-- +-- Or modify an individual function in this way: +-- +-- >>> :{ +-- env' :: Env IO +-- env' = env & advising \env -> env { +-- bar = advise (printArgs stdout "prefix ") (bar env) +-- } +-- :} +-- +-- __NOTE__: +-- +-- This module is an alternative to "Control.Monad.Dep.Advice" with two advantages: +-- +-- - It doesn't use 'Control.Monad.Dep.DepT'. The types are simpler because +-- they don't need to refer to 'Control.Monad.Dep.DepT''s environment. +-- +-- - Unlike in "Control.Monad.Dep.Advice", we can advise components +-- which work on a fixed concrete monad like 'IO'. +-- +-- Compared with "Control.Monad.Dep.Advice", it does require the extra step +-- of invoking the 'advising' helper function on a record-of-functions. +module Dep.SimpleAdvice + ( -- * Preparing components for being advised + advising, + AspectT (..), + -- * The Advice type + Advice, + + -- * Creating Advice values + makeAdvice, + makeArgsAdvice, + makeExecutionAdvice, + + -- * Applying Advices + advise, + + -- * Harmonizing Advice argument constraints + -- $restrict + restrictArgs, + + -- * Advising entire records + -- $records + adviseRecord, + + -- * "sop-core" re-exports + -- $sop + Top, + And, + All, + NP (..), + I (..), + cfoldMap_NP, + Dict (..), + ) +where + +import Dep.Has +import Dep.SimpleAdvice.Internal +import Data.Coerce +import Control.Monad +import Control.Monad.Fix +import Data.Functor.Identity +import Data.Kind +import Data.List.NonEmpty qualified as N +import Data.SOP +import Data.SOP.Dict +import Data.SOP.NP +import Data.List.NonEmpty +import Data.Typeable +import GHC.Generics qualified as G +import GHC.TypeLits +import Control.Applicative +import Control.Monad.Cont.Class +import Control.Monad.Error.Class +import Control.Monad.IO.Unlift +import Control.Monad.State.Class +import Control.Monad.Trans.Class +import Control.Monad.Trans.Identity +import Control.Monad.Writer.Class +import Control.Monad.Zip + +-- $setup +-- +-- >>> :set -XTypeApplications +-- >>> :set -XStandaloneKindSignatures +-- >>> :set -XMultiParamTypeClasses +-- >>> :set -XFunctionalDependencies +-- >>> :set -XRankNTypes +-- >>> :set -XTypeOperators +-- >>> :set -XConstraintKinds +-- >>> :set -XNamedFieldPuns +-- >>> :set -XFlexibleContexts +-- >>> :set -XDerivingStrategies +-- >>> :set -XGeneralizedNewtypeDeriving +-- >>> :set -XDataKinds +-- >>> :set -XScopedTypeVariables +-- >>> :set -XDeriveGeneric +-- >>> :set -XImportQualifiedPost +-- >>> import Dep.SimpleAdvice +-- >>> import Dep.SimpleAdvice.Basic (printArgs,returnMempty) +-- >>> import Control.Monad +-- >>> import Control.Monad.Trans +-- >>> import Control.Monad.Writer +-- >>> import Data.Kind +-- >>> import Data.SOP +-- >>> import Data.SOP.NP +-- >>> import Data.Monoid +-- >>> import System.IO +-- >>> import Data.IORef +-- >>> import GHC.Generics (Generic) +-- >>> import GHC.Generics qualified +-- >>> import Data.Function + + +-- | +-- The most general way of constructing 'Advice's. +-- +-- An 'Advice' receives the arguments of the advised +-- function packed into an n-ary product 'NP', performs some +-- effects based on them, and returns a potentially modified version of the +-- arguments, along with a function for tweaking the execution of the +-- advised function. +-- +-- >>> :{ +-- doesNothing :: forall ca m r. Monad m => Advice ca m r +-- doesNothing = makeAdvice (\args -> pure (id, args)) +-- :} +-- +-- +makeAdvice :: + forall ca m r. + -- | The function that tweaks the arguments and the execution. + ( forall as. + All ca as => + NP I as -> + AspectT m (AspectT m r -> AspectT m r, NP I as) + ) -> + Advice ca m r +makeAdvice = Advice + +-- | +-- Create an advice which only tweaks and/or analyzes the function arguments. +-- +-- >>> :{ +-- doesNothing :: forall ca m r. Monad m => Advice ca m r +-- doesNothing = makeArgsAdvice pure +-- :} +makeArgsAdvice :: + forall ca m r. + Monad m => + -- | The function that tweaks the arguments. + ( forall as. + All ca as => + NP I as -> + AspectT m (NP I as) + ) -> + Advice ca m r +makeArgsAdvice tweakArgs = + makeAdvice $ \args -> do + args' <- tweakArgs args + pure (id, args') + +-- | +-- Create an advice which only tweaks the execution of the final monadic action. +-- +-- >>> :{ +-- doesNothing :: forall ca m r. Monad m => Advice ca m r +-- doesNothing = makeExecutionAdvice id +-- :} +makeExecutionAdvice :: + forall ca m r. + Applicative m => + -- | The function that tweaks the execution. + ( AspectT m r -> + AspectT m r + ) -> + Advice ca m r +makeExecutionAdvice tweakExecution = makeAdvice \args -> pure (tweakExecution, args) + + +-- | Apply an 'Advice' to some compatible function. The function must have its +-- effects in 'AspectT', and all of its arguments must satisfy the @ca@ constraint. +-- +-- >>> :{ +-- foo :: Int -> AspectT IO String +-- foo _ = pure "foo" +-- advisedFoo = advise (printArgs stdout "Foo args: ") foo +-- :} +-- +-- __/TYPE APPLICATION REQUIRED!/__ If the @ca@ constraint of the 'Advice' remains polymorphic, +-- it must be supplied by means of a type application: +-- +-- >>> :{ +-- bar :: Int -> AspectT IO String +-- bar _ = pure "bar" +-- advisedBar1 = advise (returnMempty @Top) bar +-- advisedBar2 = advise @Top returnMempty bar +-- :} +advise :: + forall ca m r as advisee. + (Multicurryable as m r advisee, All ca as, Monad m) => + -- | The advice to apply. + Advice ca m r -> + -- | A function to be adviced. + advisee -> + advisee +advise (Advice f) advisee = do + let uncurried = multiuncurry @as @m @r advisee + uncurried' args = do + (tweakExecution, args') <- f args + tweakExecution (uncurried args') + in multicurry @as @m @r uncurried' + +-- | This function \"installs\" an 'AspectT' newtype wrapper for the monad +-- parameter of a record-of-functions, applies some function on +-- the tweaked component, and then removes the wrapper from the result. +-- +-- This is necessary because the typeclass machinery which handles +-- 'Advice's uses 'AspectT' as a \"mark\" to recognize \"the end of the function\". +advising + :: Coercible (r_ m) (r_ (AspectT m)) => + -- | transform the record coerced to 'AspectT', for example using 'adviseRecord' + (r_ (AspectT m) -> r_ (AspectT m)) -> + -- | transform the original record + r_ m -> r_ m +advising f = coerce . f . coerce + +type Multicurryable :: + [Type] -> + (Type -> Type) -> + Type -> + Type -> + Constraint +class Multicurryable as m r curried | curried -> as m r where + multiuncurry :: curried -> NP I as -> AspectT m r + multicurry :: (NP I as -> AspectT m r) -> curried + +instance Monad m => Multicurryable '[] m r (AspectT m r) where + multiuncurry action Nil = action + multicurry f = f Nil + +instance Multicurryable as m r curried => Multicurryable (a ': as) m r (a -> curried) where + multiuncurry f (I a :* as) = multiuncurry @as @m @r @curried (f a) as + multicurry f a = multicurry @as @m @r @curried (f . (:*) (I a)) + +-- $restrict +-- +-- 'Advice' values can be composed using the 'Monoid' instance, but only if +-- they have the same type parameters. It's unfortunate that—unlike with +-- normal function constraints—the @ca@ constraints of an 'Advice' aren't +-- automatically "collected" during composition. +-- +-- Instead, we need to harmonize the @ca@ constraints of each 'Advice' by +-- turning them into the combination of all constraints. 'restrictArgs' +-- helps with that. +-- +-- 'restrictArgs' takes as parameter value-level "\evidence\" that one +-- constraint implies another. But how to construct such evidence? By using +-- the 'Dict' GADT, more precisely the deceptively simple-looking term +-- @\\Dict -> Dict@. That function "absorbs" some constraint present in the +-- ambient context and re-packages it a a new constraint that is implied by +-- the former. We can't rely on type inference here; we need to provide +-- enough type information to the GADT, be it as an explicit signature: +-- +-- >>> :{ +-- stricterPrintArgs :: forall m r. MonadIO m => Advice (Show `And` Eq `And` Ord) m r +-- stricterPrintArgs = restrictArgs (\Dict -> Dict) (printArgs stdout "foo") +-- :} +-- +-- or with a type application to 'restrictArgs': +-- +-- >>> stricterPrintArgs = restrictArgs @(Show `And` Eq `And` Ord) (\Dict -> Dict) (printArgs stdout "foo") + +-- | Makes the constraint on the arguments more restrictive. +restrictArgs :: + forall more less m r. + -- | Evidence that one constraint implies the other. Every @x@ that has a @more@ instance also has a @less@ instance. + (forall x. Dict more x -> Dict less x) -> + -- | Advice with less restrictive constraint on the args. + Advice less m r -> + -- | Advice with more restrictive constraint on the args. + Advice more m r +-- about the order of the type parameters... which is more useful? +-- A possible principle to follow: +-- We are likely to know the "less" constraint, because advices are likely to +-- come pre-packaged and having a type signature. +-- We arent' so sure about having a signature for a whole composed Advice, +-- because the composition might be done +-- on the fly, while constructing a record, without a top-level binding with a +-- type signature. This seems to favor putting "more" first. +restrictArgs evidence (Advice advice) = Advice \args -> + let advice' :: forall as. All more as => NP I as -> AspectT m (AspectT m r -> AspectT m r, NP I as) + advice' args' = + case Data.SOP.Dict.mapAll @more @less evidence of + f -> case f (Dict @(All more) @as) of + Dict -> advice args' + in advice' args + + +-- advising *all* fields of a record +-- +-- +type AdvisedRecord :: (Type -> Constraint) -> (Type -> Type) -> (Type -> Constraint) -> ((Type -> Type) -> Type) -> Constraint +class AdvisedRecord ca m cr advised where + _adviseRecord :: [(TypeRep, String)] -> (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca m r) -> advised (AspectT m) -> advised (AspectT m) + +type AdvisedProduct :: (Type -> Constraint) -> (Type -> Type) -> (Type -> Constraint) -> (k -> Type) -> Constraint +class AdvisedProduct ca m cr advised_ where + _adviseProduct :: TypeRep -> [(TypeRep, String)] -> (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca m r) -> advised_ k -> advised_ k + +instance + ( G.Generic (advised (AspectT m)), + -- G.Rep (advised (AspectT m)) ~ G.D1 ('G.MetaData name mod p nt) (G.C1 y advised_), + G.Rep (advised (AspectT m)) ~ G.D1 x (G.C1 y advised_), + Typeable advised, + AdvisedProduct ca m cr advised_ + ) => + AdvisedRecord ca m cr advised + where + _adviseRecord acc f unadvised = + let G.M1 (G.M1 unadvised_) = G.from unadvised + advised_ = _adviseProduct @_ @ca @m @cr (typeRep (Proxy @advised)) acc f unadvised_ + in G.to (G.M1 (G.M1 advised_)) + +instance + ( AdvisedProduct ca m cr advised_left, + AdvisedProduct ca m cr advised_right + ) => + AdvisedProduct ca m cr (advised_left G.:*: advised_right) + where + _adviseProduct tr acc f (unadvised_left G.:*: unadvised_right) = _adviseProduct @_ @ca @m @cr tr acc f unadvised_left G.:*: _adviseProduct @_ @ca @m @cr tr acc f unadvised_right + +data RecordComponent + = Terminal + | IWrapped + | Recurse + +type DiscriminateAdvisedComponent :: Type -> RecordComponent +type family DiscriminateAdvisedComponent c where + DiscriminateAdvisedComponent (a -> b) = Terminal + DiscriminateAdvisedComponent (AspectT m x) = Terminal + DiscriminateAdvisedComponent (Identity _) = IWrapped + DiscriminateAdvisedComponent (I _) = IWrapped + DiscriminateAdvisedComponent _ = Recurse + +type AdvisedComponent :: RecordComponent -> (Type -> Constraint) -> (Type -> Type) -> (Type -> Constraint) -> Type -> Constraint +class AdvisedComponent component_type ca m cr advised where + _adviseComponent :: [(TypeRep, String)] -> (forall r. cr r => NonEmpty (TypeRep, String) -> Advice ca m r) -> advised -> advised + +instance + ( AdvisedComponent (DiscriminateAdvisedComponent advised) ca m cr advised, + KnownSymbol fieldName + ) => + AdvisedProduct ca m cr (G.S1 ( 'G.MetaSel ( 'Just fieldName) su ss ds) (G.Rec0 advised)) + where + _adviseProduct tr acc f (G.M1 (G.K1 advised)) = + let acc' = (tr, symbolVal (Proxy @fieldName)) : acc + in G.M1 (G.K1 (_adviseComponent @(DiscriminateAdvisedComponent advised) @ca @m @cr acc' f advised)) + +instance + AdvisedRecord ca m cr advisable => + AdvisedComponent Recurse ca m cr (advisable (AspectT m)) + where + _adviseComponent acc f advised = _adviseRecord @ca @m @cr acc f advised + +instance + (Multicurryable as m r advised, All ca as, cr r, Monad m) => + AdvisedComponent Terminal ca m cr advised + where + _adviseComponent acc f advised = advise @ca @m (f (Data.List.NonEmpty.fromList acc)) advised + +instance + AdvisedComponent (DiscriminateAdvisedComponent advised) ca m cr advised => + AdvisedComponent IWrapped ca m cr (Identity advised) + where + _adviseComponent acc f (Identity advised) = Identity (_adviseComponent @(DiscriminateAdvisedComponent advised) @ca @m @cr acc f advised) + +instance + AdvisedComponent (DiscriminateAdvisedComponent advised) ca m cr advised => + AdvisedComponent IWrapped ca m cr (I advised) + where + _adviseComponent acc f (I advised) = I (_adviseComponent @(DiscriminateAdvisedComponent advised) @ca @m @cr acc f advised) + +-- | Gives 'Advice' to all the functions in a record-of-functions. +-- +-- The function that builds the advice receives a list of tuples @(TypeRep, String)@ +-- which represent the record types and fields names we have +-- traversed until arriving at the advised function. This info can be useful for +-- logging advices. It's a list instead of a single tuple because +-- 'adviseRecord' works recursively. The elements come innermost-first. +-- +-- __/TYPE APPLICATION REQUIRED!/__ The @ca@ constraint on function arguments +-- and the @cr@ constraint on the result type must be supplied by means of a +-- type application. Supply 'Top' if no constraint is required. +adviseRecord :: + forall ca cr m advised. + AdvisedRecord ca m cr advised => + -- | The advice to apply. + (forall r . cr r => NonEmpty (TypeRep, String) -> Advice ca m r) -> + -- | The record to advise recursively. + advised (AspectT m) -> + -- | The advised record. + advised (AspectT m) +adviseRecord = _adviseRecord @ca @m @cr [] + +-- $sop +-- Some useful definitions re-exported the from \"sop-core\" package. +-- +-- 'NP' is an n-ary product used to represent the arguments of advised functions. +-- +-- 'I' is an identity functor. The arguments processed by an 'Advice' come wrapped in it. +-- +-- 'cfoldMap_NP' is useful to construct homogeneous lists out of the 'NP' product, for example: +-- +-- >>> cfoldMap_NP (Proxy @Show) (\(I a) -> [show a]) (I False :* I (1::Int) :* Nil) +-- ["False","1"] + +-- $constraints +-- +-- Some useful definitions re-exported the from \"constraints\" package. +-- +-- 'Dict' and '(:-)' are GADTs used to capture and transform constraints. Used in the 'restrictArgs' function. + +-- $constrainthelpers +-- +-- To help with the constraint @ca@ that parameterizes 'Advice', this library re-exports the following helpers from \"sop-core\": +-- +-- * 'Top' is the \"always satisfied\" constraint, useful when whe don't want to require anything specific in @ca@. +-- +-- * 'And' combines two constraints so that an 'Advice' can request them both, for example @Show \`And\` Eq@. +-- +-- Also, the 'All' constraint says that some constraint is satisfied by all the +-- components of an 'NP' product. It's in scope when processing the function +-- arguments inside an 'Advice'. + +-- $invocation +-- These functions are helpers for running 'AspectT' computations, beyond what 'runAspectT' provides. +-- +-- They aren't directly related to 'Advice's, but they require some of the same machinery, and that's why they are here.
+ lib/Dep/SimpleAdvice/Basic.hs view
@@ -0,0 +1,238 @@+{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneKindSignatures #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE TypeSynonymInstances #-} +{-# LANGUAGE FlexibleInstances #-} + +-- | +-- This module contains basic examples advices. +-- +-- __/BEWARE!/__ These are provided for illustrative purposes only, they +-- strive for simplicity and not robustness or efficiency. +-- +-- They can be converted to @DepT@-based 'Dep.Advice.Advice's using 'Dep.Advice.fromSimple'. +module Dep.SimpleAdvice.Basic + ( -- * Basic advices + returnMempty, + printArgs, + AnyEq (..), + doCachingBadly, + doAsyncBadly, + injectFailures, + -- ** Synthetic call stacks + MethodName, + StackFrame, + SyntheticCallStack, + SyntheticCallStackException (..), + HasSyntheticCallStack (..), + keepCallStack + ) +where + +import Dep.SimpleAdvice +import Control.Monad.Reader +import Control.Monad.Trans +import Data.Proxy +import Data.Functor.Constant +import Data.Functor.Identity +import Data.SOP +import Data.SOP (hctraverse_) +import Data.SOP.NP +import Data.Type.Equality +import System.IO +import Control.Concurrent +import Control.Monad.IO.Unlift +import Data.IORef +import Control.Exception +import Type.Reflection +import qualified Data.Typeable as T +import Data.List.NonEmpty (NonEmpty (..)) +import Data.List.NonEmpty qualified as NonEmpty + +-- $setup +-- +-- >>> :set -XTypeApplications +-- >>> :set -XStandaloneKindSignatures +-- >>> :set -XMultiParamTypeClasses +-- >>> :set -XFunctionalDependencies +-- >>> :set -XRankNTypes +-- >>> :set -XTypeOperators +-- >>> :set -XConstraintKinds +-- >>> :set -XNamedFieldPuns +-- >>> :set -XFlexibleContexts +-- >>> :set -XFlexibleInstances +-- >>> :set -XAllowAmbiguousTypes +-- >>> :set -XBlockArguments +-- >>> import Control.Monad +-- >>> import Control.Monad.Trans +-- >>> import Dep.SimpleAdvice +-- >>> import Dep.SimpleAdvice.Basic (printArgs,returnMempty) +-- >>> import Data.Kind +-- >>> import Data.SOP +-- >>> import Data.SOP.NP +-- >>> import Data.Monoid +-- >>> import System.IO +-- >>> import Data.IORef + + + +-- | Makes functions discard their result and always return 'mempty'. +-- +returnMempty :: forall ca m r. (Monad m, Monoid r) => Advice ca m r +returnMempty = + makeExecutionAdvice + ( \action -> do + _ <- action + pure (mempty :: r) + ) + +-- | Given a 'Handle' and a prefix string, makes functions print their +-- arguments to the 'Handle'. +-- +printArgs :: forall m r. MonadIO m => Handle -> String -> Advice Show m r +printArgs h prefix = + makeArgsAdvice + ( \args -> do + liftIO $ hPutStr h $ prefix ++ ":" + hctraverse_ (Proxy @Show) (\(I a) -> liftIO (hPutStr h (" " ++ show a))) args + liftIO $ hPutStrLn h "\n" + liftIO $ hFlush h + pure args + ) + +-- | A helper datatype for universal equality comparisons of existentialized values, used by 'doCachingBadly'. +-- +-- For a more complete elaboration of this idea, see the the \"exinst\" package. +data AnyEq where + AnyEq :: forall a. (Typeable a, Eq a) => a -> AnyEq + +instance Eq AnyEq where + AnyEq any1 == AnyEq any2 = + case testEquality (typeOf any1) (typeOf any2) of + Nothing -> False + Just Refl -> any1 == any2 + +-- | +-- Given the means for looking up and storing @r@ values in the underlying +-- monad @m@, makes functions (inefficiently) cache their results. +-- +-- The monad @m@ and the result type @r@ must be known before building the +-- advice. So, once built, this 'Advice' won't be polymorphic over them. +-- +-- The implementation of this function makes use of the existential type +-- parameter @u@ of 'makeAdvice', because the phase that processes the function +-- arguments needs to communicate the calculated `AnyEq` cache key to the phase +-- that processes the function result. +-- +-- A better implementation of this advice would likely use an @AnyHashable@ +-- helper datatype for the keys. +doCachingBadly :: forall m r. Monad m => (AnyEq -> m (Maybe r)) -> (AnyEq -> r -> m ()) -> Advice (Eq `And` Typeable) m r +doCachingBadly cacheLookup cachePut = makeAdvice \args -> + let key = AnyEq $ cfoldMap_NP (Proxy @(And Eq Typeable)) (\(I a) -> [AnyEq a]) $ args + tweakExecution action = do + mr <- lift $ cacheLookup key + case mr of + Nothing -> do + r <- action + lift $ cachePut key r + pure r + Just r -> + pure r + in pure (tweakExecution, args) + +-- | Makes functions that return `()` launch asynchronously. +-- +-- A better implementation of this advice would likely use the \"async\" +-- package instead of bare `forkIO`. +doAsyncBadly :: forall ca m . MonadUnliftIO m => Advice ca m () +doAsyncBadly = makeExecutionAdvice \action -> do + _ <- withRunInIO (\unlift -> forkIO (unlift action)) + pure () + + +-- | Given a reference with two infinite lists of 'IO' actions, on each +-- invocation of the advised function, take an action from the first list and +-- execute it before, and take one action from the second list and execute it +-- after. +-- +-- A common use for this would be to pass exception-throwing actions. +injectFailures :: forall ca m r . (MonadIO m, MonadFail m) => IORef ([IO ()], [IO ()]) -> Advice ca m r +injectFailures ref = makeExecutionAdvice \action -> do + (before, after) <- liftIO $ atomicModifyIORef' ref \(before : befores, after : afters) -> ((befores, afters), (before, after)) + liftIO before + r <- action + liftIO after + pure r + + +-- Synthetic call stacks +-- + + +type MethodName = String + +-- | The typeable representation of the record which contains the invoked +-- function, along with the field name of the invoked function. +type StackFrame = (T.TypeRep, MethodName) + +type SyntheticCallStack = [StackFrame] + +-- | Wraps an exception along with a 'SyntheticCallStack'. +data SyntheticCallStackException + = SyntheticCallStackException SomeException SyntheticCallStack + deriving stock Show + +instance Exception SyntheticCallStackException + +-- | Class of environments that carry a 'SyntheticCallStack' value that can be +-- modified. +class HasSyntheticCallStack e where + -- | A lens from the environment to the call stack. + callStack :: forall f . Functor f => (SyntheticCallStack -> f SyntheticCallStack) -> e -> f e + +-- | The trivial case, useful when 'SyntheticCallStack' is the environment type +-- of a 'Control.Monad.Reader.ReaderT'. +instance HasSyntheticCallStack SyntheticCallStack where + callStack = id + +-- | If the environment carries a 'SyntheticCallStack', make advised functions add +-- themselves to the 'SyntheticCallStack' before they start executing. +-- +-- This 'Dep.SimpleAdvice.Advice' requires a reader-like base monad to work. It +-- doesn't need to be 'Control.Monad.Dep.DepT', it can be regular a +-- 'Control.Monad.Reader.ReaderT'. +-- +-- Caught exceptions are rethrown wrapped in 'SyntheticCallStackException's, +-- with the current 'SyntheticCallStack' added. +keepCallStack :: + (MonadUnliftIO m, MonadReader runenv m, HasSyntheticCallStack runenv, Exception e) => + -- | A selector for the kinds of exceptions we want to catch. + -- For example @fromException \@IOError@. + (SomeException -> Maybe e) -> + -- | The path to the current component/method in the environment. Only the + -- head is used. It will be usually obtained through + -- 'Dep.SimpleAdvice.adviseRecord'. + NonEmpty (T.TypeRep, MethodName) -> + Advice ca m r +keepCallStack selector (NonEmpty.head -> method) = makeExecutionAdvice \action -> do + currentStack <- asks (view callStack) + withRunInIO \unlift -> do + er <- tryJust selector (unlift (local (over callStack (method :)) action)) + case er of + Left e -> throwIO (SyntheticCallStackException (toException e) (method : currentStack)) + Right r -> pure r + where + view l = getConstant . l Constant + over l f = runIdentity . l (Identity . f) + + +
+ lib/Dep/SimpleAdvice/Internal.hs view
@@ -0,0 +1,126 @@+{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE FunctionalDependencies #-} +{-# LANGUAGE GADTSyntax #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneKindSignatures #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE UndecidableSuperClasses #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE BlockArguments #-} + +module Dep.SimpleAdvice.Internal where + +import Dep.Has +import Data.Coerce +import Control.Monad +import Control.Monad.Fix +import Data.Functor.Identity +import Data.Kind +import Data.List.NonEmpty qualified as N +import Data.SOP +import Data.SOP.Dict +import Data.SOP.NP +import Data.Typeable +import GHC.Generics qualified as G +import GHC.TypeLits +import Control.Applicative +import Control.Monad.Reader +import Control.Monad.Cont.Class +import Control.Monad.Error.Class +import Control.Monad.IO.Unlift +import Control.Monad.State.Class +import Control.Monad.Trans.Class +import Control.Monad.Trans.Identity +import Control.Monad.Writer.Class +import Control.Monad.Zip + +-- | A generic transformation of 'AspectT'-effectful functions with +-- base monad @m@ and return type @r@, +-- provided the functions satisfy certain constraint @ca@ +-- on all of their arguments. +-- +-- 'Advice's that don't care about the @ca@ constraint (because they don't +-- touch function arguments) can leave it polymorphic, and this facilitates +-- 'Advice' composition, but then the constraint must be given the catch-all +-- `Top` value (using a type application) at the moment of calling 'advise'. +-- +-- See "Control.Monad.Dep.SimpleAdvice.Basic" for examples. +type Advice :: + (Type -> Constraint) -> + (Type -> Type) -> + Type -> + Type +data Advice (ca :: Type -> Constraint) m r where + Advice :: + forall ca m r. + ( forall as. + All ca as => + NP I as -> + AspectT m (AspectT m r -> AspectT m r, NP I as) + ) -> + Advice ca m r + + +-- | +-- 'Advice's compose \"sequentially\" when tweaking the arguments, and +-- \"concentrically\" when tweaking the final 'AspectT' action. +-- +-- The first 'Advice' is the \"outer\" one. It tweaks the function arguments +-- first, and wraps around the execution of the second, \"inner\" 'Advice'. +instance Monad m => Semigroup (Advice ca m r) where + Advice outer <> Advice inner = Advice \args -> do + (tweakOuter, argsOuter) <- outer args + (tweakInner, argsInner) <- inner argsOuter + pure (tweakOuter . tweakInner, argsInner) + +instance Monad m => Monoid (Advice ca m r) where + mappend = (<>) + mempty = Advice \args -> pure (id, args) + + +-- | This transformer is isomorphic to 'Control.Monad.Trans.Identity.IdentityT'. +-- +-- It doesn't really do anything, it only helps the typeclass machinery. +type AspectT :: + (Type -> Type) -> + Type -> + Type +newtype AspectT (m :: Type -> Type) (r :: Type) = AspectT {runAspectT :: m r} + deriving + ( Functor, + Applicative, + Alternative, + Monad, + MonadFix, + MonadFail, + MonadZip, + MonadPlus, + MonadCont, + MonadIO, + MonadUnliftIO + ) + +instance MonadTrans AspectT where + lift = AspectT + +deriving newtype instance MonadReader env m => MonadReader env (AspectT m) +deriving newtype instance MonadState s m => MonadState s (AspectT m) +deriving newtype instance MonadWriter w m => MonadWriter w (AspectT m) +deriving newtype instance MonadError e m => MonadError e (AspectT m) + +data Pair a b = Pair !a !b +
test/simple-advice-tests.hs view
@@ -26,10 +26,10 @@ import Prelude hiding (log) import Barbies +import Dep.Has +import Dep.SimpleAdvice +import Dep.SimpleAdvice.Basic import Control.Monad.Dep -import Control.Monad.Dep.SimpleAdvice -import Control.Monad.Dep.SimpleAdvice.Basic -import Control.Monad.Dep.Has import Control.Monad.Reader import Control.Monad.Writer import Data.List.NonEmpty
+ test/synthetic-callstack-tests.hs view
@@ -0,0 +1,349 @@+{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE StandaloneKindSignatures #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE ViewPatterns #-} + +-- | An example of how an application can make use of the "dep-t" and +-- "dep-t-advice" packages for keeping a "synthetic" call stack that tracks the +-- invocations of monadic functions—though only of those which take part in dependency +-- injection. +-- +-- We are assuming that the application follows a "record-of-functions" style. +module Main (main) where + +import Control.Exception +import Control.Monad.Dep (DepT) +import Control.Monad.IO.Unlift +import Control.Monad.Reader +import Control.Monad.Trans.Cont +import Data.Function ((&)) +import Data.Functor ((<&>)) +import Data.Functor.Compose +import Data.Functor.Constant +import Data.Functor.Identity +import Data.IORef +import Data.List.NonEmpty (NonEmpty (..)) +import Data.List.NonEmpty qualified as NonEmpty +import Data.Set (Set) +import Data.Set qualified as Set +import Data.Typeable +import Dep.Advice qualified as A +import Dep.Env + ( Autowireable, + Autowired (..), + Constructor, + DemotableFieldNames, + FieldsFindableByType, + Phased, + bindPhase, + constructor, + fixEnv, + pullPhase, + skipPhase, + ) +import Dep.Has + ( Has (dep), + asCall, + ) +import Dep.SimpleAdvice + ( Advice, + AspectT (..), + Top, + adviseRecord, + advising, + makeExecutionAdvice, + ) +import Dep.SimpleAdvice.Basic (HasSyntheticCallStack (callStack), MethodName, StackFrame, SyntheticCallStack, SyntheticCallStackException (SyntheticCallStackException), injectFailures, keepCallStack) +import GHC.Generics (Generic) +import GHC.TypeLits +import Lens.Micro (Lens', lens) +import System.IO +import Test.Tasty +import Test.Tasty.HUnit +import Prelude hiding (insert, lookup) + +-- THE BUSINESS LOGIC +-- +-- + +-- Component interfaces, defined as records polymorphic over the effect monad. +newtype Logger m = Logger + { emitMsg :: String -> m () + } + deriving stock (Generic) + +data Repository m = Repository + { insert :: Int -> m (), + lookup :: Int -> m Bool + } + deriving stock (Generic) + +newtype Controller m = Controller + { -- insert one arg, look up the other. Nonsensical, but good enough for an example. + route :: Int -> Int -> m Bool + } + deriving stock (Generic) + +-- Component implementations, some of which depend on other components. +-- +makeStdoutLogger :: MonadIO m => Logger m +makeStdoutLogger = Logger \msg -> liftIO $ putStrLn msg + +-- allocation helper. +allocateSet :: Allocator (IORef (Set Int)) +allocateSet = ContT $ bracket (newIORef Set.empty) pure + +-- When a component depends on another, it does so by taking an "env" parameter +-- in the constructor and requiring 'Has' constraints on it. +makeInMemoryRepository :: + (Has Logger m env, MonadIO m) => + IORef (Set Int) -> + env -> + Repository m +makeInMemoryRepository ref (asCall -> call) = do + Repository + { insert = \key -> do + call emitMsg "inserting..." + theSet <- liftIO $ readIORef ref + liftIO $ writeIORef ref $ Set.insert key theSet, + lookup = \key -> do + call emitMsg "looking..." + theSet <- liftIO $ readIORef ref + pure (Set.member key theSet) + } + +-- This implementation of Controller depends both on the Logger and the +-- Repository. +-- +-- In general, the graph of dependencies between components can be a complex +-- directed acyclic graph. +makeController :: + (Has Logger m env, Has Repository m env, Monad m) => + env -> + Controller m +makeController (asCall -> call) = + Controller + { route = \toInsert toLookup -> do + call emitMsg "serving..." + call insert toInsert + call lookup toLookup + } + +-- THE COMPOSITION ROOT +-- +-- Here we define our dependency injection environment. +-- +-- We put all the components which will form part of our application in an +-- environment record. +-- +-- Each field is wrapped in a functor `h` which controls the "phases" we must +-- go through in the construction of the environment. When `h` becomes +-- Identity, the environment is ready for use. (This is an example of the +-- "Higer-Kinded Data" pattern.) +data Env h m = Env + { logger :: h (Logger m), + repository :: h (Repository m), + controller :: h (Controller m) + } + deriving stock (Generic) + deriving anyclass (Phased, DemotableFieldNames, FieldsFindableByType) + +-- Locate the components by their types. We could also define the required Has +-- instance for each component manually, but that's tedious. +deriving via Autowired (Env Identity m) instance Autowireable r_ m (Env Identity m) => Has r_ m (Env Identity m) + +-- The "phases" that components go through until fully built. Each phase +-- is represented as an applicative functor. The succession of phases is +-- defined using Data.Functor.Compose. +-- + +-- A phase in which we might allocate some resource needed by the component, +-- also set some bracket-like resource management. +-- The "managed" library could be used instead of ContT. +type Allocator = ContT () IO + +-- First we allocate any needed resource, then we have a construction phase +-- during which the component reads its own dependencies from a "completed" +-- environment. +-- +-- There could be more phases, like for example an initial "read configuration" +-- phase. +type Phases env_ m = Allocator `Compose` Constructor env_ m + +-- Environment value +-- +-- The base monad is a 'ReaderT' holding a SyntheticCallStack value which gets modified +-- using "local" for each sub-call. +-- +-- Notice that neither the interfaces nor the implementations which we defined +-- earlier knew anything about the ReaderT. +env :: Env (Phases Env (ReaderT SyntheticCallStack IO)) (ReaderT SyntheticCallStack IO) +env = + Env + { logger = + allocateBombs 1 `bindPhase` \bombs -> + constructor (\_ -> makeStdoutLogger) + <&> advising + ( adviseRecord @Top @Top \method -> + keepCallStack ioEx method <> injectFailures bombs + ), + repository = + allocateSet `bindPhase` \ref -> + constructor (makeInMemoryRepository ref) + <&> advising + ( adviseRecord @Top @Top \method -> + keepCallStack ioEx method + ), + controller = + skipPhase @Allocator $ + constructor makeController + <&> advising + ( adviseRecord @Top @Top \method -> + keepCallStack ioEx method + ) + } + +-- Catch only IOExceptions for this example. +ioEx :: SomeException -> Maybe IOError +ioEx = fromException @IOError + +-- Allocate a supply of potentially exception-throwing actions. +allocateBombs :: Int -> Allocator (IORef ([IO ()], [IO ()])) +allocateBombs whenToBomb = ContT $ bracket (newIORef bombs) pure + where + bombs = + ( replicate whenToBomb (pure ()) ++ repeat (throwIO (userError "oops")), + repeat (pure ()) + ) + +-- THE COMPOSITION ROOT - ALTERNATIVE APPROACH +-- +-- +-- Here we'll define the dependency injection environment in a slightly +-- different way (but reusing both the "business logic" and the Env type). + +-- The basic idea is that we don't perform dependency injection as a separate +-- Applicative phase (so no Constructor, but a mere Identity phase). +-- +-- Instead, we shift that task into the base monad's environent. + +-- As a result the "phases" are simpler: +type Phases' = Allocator `Compose` Identity + +-- Now the expanded "runtime" environment will hold both the synthetic call +-- stack and the components. We define this small helper datatype for that. It +-- augments a preexisting environment with call-related info. +data CallEnv i e_ m = CallEnv + { _callInfo :: i, + _ops :: e_ m + } + +-- Delegate all 'Has' queries to the inner environment. +instance Has r_ m (e_ m) => Has r_ m (CallEnv i e_ m) where + dep = dep . _ops + +instance HasSyntheticCallStack (CallEnv SyntheticCallStack e_ m) where + callStack = lens _callInfo (\(CallEnv _ ops) i' -> CallEnv i' ops) + +-- Here use the DepT monad (a variant of ReaderT) as the base monad. +-- +-- The environment of DepT includes—just as before—the SyntheticCallStack value +-- that is used to trace each sub-call. +-- +-- But now it also includes the dependency injection context with all the +-- components. +env' :: Env Phases' (DepT (CallEnv SyntheticCallStack (Env Identity)) IO) +env' = + Env + { logger = + allocateBombs 1 `bindPhase` \bombs -> + Identity (A.component \_ -> makeStdoutLogger) + <&> ( A.adviseRecord @Top @Top \method -> + A.fromSimple_ (keepCallStack ioEx method <> injectFailures bombs) + ), + repository = + allocateSet `bindPhase` \ref -> + Identity (A.component (makeInMemoryRepository ref)) + <&> ( A.adviseRecord @Top @Top \method -> + A.fromSimple_ (keepCallStack ioEx method) + ), + controller = + skipPhase @Allocator $ + Identity (A.component makeController) + <&> ( A.adviseRecord @Top @Top \method -> + A.fromSimple_ (keepCallStack ioEx method) + ) + } + +-- TESTS +-- +-- +expectedException :: (IOError, SyntheticCallStack) +expectedException = + ( userError "oops", + [ (typeRep (Proxy @Logger), "emitMsg"), + (typeRep (Proxy @Repository), "insert"), + (typeRep (Proxy @Controller), "route") + ] + ) + +-- Test the "Constructor"-based version of the environment. +testSyntheticCallStack :: Assertion +testSyntheticCallStack = do + let action = + runContT (pullPhase @Allocator env) \constructors -> do + -- here we complete the construction of the environment + let (asCall -> call) = fixEnv constructors + flip + runReaderT + ([] :: SyntheticCallStack) -- the initial stack trace for the call + ( do + _ <- call route 1 2 + pure () + ) + me <- try @SyntheticCallStackException action + case me of + Left (SyntheticCallStackException (fromException @IOError -> Just ex) trace) -> + assertEqual "exception with callstack" expectedException (ex, trace) + Right _ -> assertFailure "expected exception did not appear" + +-- Test the "DepT"-based version of the environment. +testSyntheticCallStack' :: Assertion +testSyntheticCallStack' = do + let action = + runContT (pullPhase @Allocator env') \runnable -> do + _ <- A.runFromDep (pure (CallEnv [] runnable)) route 1 2 + pure () + me <- try @SyntheticCallStackException action + case me of + Left (SyntheticCallStackException (fromException @IOError -> Just ex) trace) -> + assertEqual "exception with callstack" expectedException (ex, trace) + Right _ -> assertFailure "expected exception did not appear" + +tests :: TestTree +tests = + testGroup + "All" + [ testCase "synthetic call stack" testSyntheticCallStack, + testCase "synthetic call stack - DepT" testSyntheticCallStack' + ] + +main :: IO () +main = defaultMain tests
test/tests-has-conversion.hs view
@@ -26,10 +26,10 @@ import Prelude hiding (log) import Control.Monad.Dep -import Control.Monad.Dep.Has -import Control.Monad.Dep.Env -import Control.Monad.Dep.Advice -import Control.Monad.Dep.Advice.Basic +import Dep.Has +import Dep.Env +import Dep.Advice +import Dep.Advice.Basic import Control.Monad.Reader import Control.Monad.Writer import Control.Monad.RWS
test/tests.hs view
@@ -18,10 +18,10 @@ module Main (main) where +import Dep.Advice +import Dep.Advice.Basic +import Dep.SimpleAdvice.Basic import Control.Monad.Dep -import Control.Monad.Dep.Advice -import Control.Monad.Dep.Advice.Basic -import Control.Monad.Dep.SimpleAdvice.Basic import Control.Monad.Reader import Control.Monad.Writer import Control.Monad.RWS
test/weird-advice-tests.hs view
@@ -24,9 +24,9 @@ module Main (main) where import Barbies +import Dep.Advice +import Dep.Has import Control.Monad.Dep -import Control.Monad.Dep.Advice -import Control.Monad.Dep.Has import Control.Monad.Reader import Control.Monad.Writer import Data.Coerce