packages feed

effect-stack 0.2.1 → 0.3

raw patch · 5 files changed

+579/−148 lines, 5 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Control.Monad.Stack.Except: class Monad m => ErrorStack m where {
- Control.Monad.Stack.Except: depthError :: forall n m a. ErrorConstraints n m => ErrorDepth n m a -> m a
- Control.Monad.Stack.Except: instance (Control.Monad.Stack.Except.ErrorStack m, GHC.Base.Monoid w) => Control.Monad.Stack.Except.ErrorStack (Control.Monad.Trans.Accum.AccumT w m)
- Control.Monad.Stack.Except: instance (Control.Monad.Stack.Except.ErrorStack m, GHC.Base.Monoid w) => Control.Monad.Stack.Except.ErrorStack (Control.Monad.Trans.RWS.CPS.RWST r w s m)
- Control.Monad.Stack.Except: instance (Control.Monad.Stack.Except.ErrorStack m, GHC.Base.Monoid w) => Control.Monad.Stack.Except.ErrorStack (Control.Monad.Trans.RWS.Lazy.RWST r w s m)
- Control.Monad.Stack.Except: instance (Control.Monad.Stack.Except.ErrorStack m, GHC.Base.Monoid w) => Control.Monad.Stack.Except.ErrorStack (Control.Monad.Trans.RWS.Strict.RWST r w s m)
- Control.Monad.Stack.Except: instance (Control.Monad.Stack.Except.ErrorStack m, GHC.Base.Monoid w) => Control.Monad.Stack.Except.ErrorStack (Control.Monad.Trans.Writer.CPS.WriterT w m)
- Control.Monad.Stack.Except: instance (Control.Monad.Stack.Except.ErrorStack m, GHC.Base.Monoid w) => Control.Monad.Stack.Except.ErrorStack (Control.Monad.Trans.Writer.Lazy.WriterT w m)
- Control.Monad.Stack.Except: instance (Control.Monad.Stack.Except.ErrorStack m, GHC.Base.Monoid w) => Control.Monad.Stack.Except.ErrorStack (Control.Monad.Trans.Writer.Strict.WriterT w m)
- Control.Monad.Stack.Except: instance Control.Monad.Stack.Except.ErrorStack m => Control.Monad.Stack.Except.ErrorStack (Control.Monad.Trans.Cont.ContT r m)
- Control.Monad.Stack.Except: instance Control.Monad.Stack.Except.ErrorStack m => Control.Monad.Stack.Except.ErrorStack (Control.Monad.Trans.Identity.IdentityT m)
- Control.Monad.Stack.Except: instance Control.Monad.Stack.Except.ErrorStack m => Control.Monad.Stack.Except.ErrorStack (Control.Monad.Trans.Maybe.MaybeT m)
- Control.Monad.Stack.Except: instance Control.Monad.Stack.Except.ErrorStack m => Control.Monad.Stack.Except.ErrorStack (Control.Monad.Trans.Reader.ReaderT r m)
- Control.Monad.Stack.Except: instance Control.Monad.Stack.Except.ErrorStack m => Control.Monad.Stack.Except.ErrorStack (Control.Monad.Trans.Select.SelectT r m)
- Control.Monad.Stack.Except: instance Control.Monad.Stack.Except.ErrorStack m => Control.Monad.Stack.Except.ErrorStack (Control.Monad.Trans.State.Lazy.StateT s m)
- Control.Monad.Stack.Except: instance Control.Monad.Stack.Except.ErrorStack m => Control.Monad.Stack.Except.ErrorStack (Control.Monad.Trans.State.Strict.StateT s m)
- Control.Monad.Stack.Except: instance GHC.Base.Monad m => Control.Monad.Stack.Except.ErrorStack (Control.Monad.Trans.Except.ExceptT e m)
- Control.Monad.Stack.Except: liftError :: ErrorStack m => PopError m a -> m a
- Control.Monad.Stack.Except: type ErrorConstraints n m = (KnownNat n, StackConstraints n ExceptT ErrorStack m)
- Control.Monad.Stack.Except: type ErrorDepth n m = IteratePop n ExceptT m
- Control.Monad.Stack.Except: type MonadErrorDepth n m e = (ErrorConstraints n m, MonadError e (ErrorDepth n m))
- Control.Monad.Stack.Except: type family PopError m :: * -> *;
- Control.Monad.Stack.Except: }
+ Control.Monad.Stack.Error: class Monad m => ErrorStack m where {
+ Control.Monad.Stack.Error: depthError :: forall n m a. ErrorConstraints n m => ErrorDepth n m a -> m a
+ Control.Monad.Stack.Error: instance (Control.Monad.Stack.Error.ErrorStack m, GHC.Base.Monoid w) => Control.Monad.Stack.Error.ErrorStack (Control.Monad.Trans.Accum.AccumT w m)
+ Control.Monad.Stack.Error: instance (Control.Monad.Stack.Error.ErrorStack m, GHC.Base.Monoid w) => Control.Monad.Stack.Error.ErrorStack (Control.Monad.Trans.RWS.CPS.RWST r w s m)
+ Control.Monad.Stack.Error: instance (Control.Monad.Stack.Error.ErrorStack m, GHC.Base.Monoid w) => Control.Monad.Stack.Error.ErrorStack (Control.Monad.Trans.RWS.Lazy.RWST r w s m)
+ Control.Monad.Stack.Error: instance (Control.Monad.Stack.Error.ErrorStack m, GHC.Base.Monoid w) => Control.Monad.Stack.Error.ErrorStack (Control.Monad.Trans.RWS.Strict.RWST r w s m)
+ Control.Monad.Stack.Error: instance (Control.Monad.Stack.Error.ErrorStack m, GHC.Base.Monoid w) => Control.Monad.Stack.Error.ErrorStack (Control.Monad.Trans.Writer.CPS.WriterT w m)
+ Control.Monad.Stack.Error: instance (Control.Monad.Stack.Error.ErrorStack m, GHC.Base.Monoid w) => Control.Monad.Stack.Error.ErrorStack (Control.Monad.Trans.Writer.Lazy.WriterT w m)
+ Control.Monad.Stack.Error: instance (Control.Monad.Stack.Error.ErrorStack m, GHC.Base.Monoid w) => Control.Monad.Stack.Error.ErrorStack (Control.Monad.Trans.Writer.Strict.WriterT w m)
+ Control.Monad.Stack.Error: instance Control.Monad.Stack.Error.ErrorStack m => Control.Monad.Stack.Error.ErrorStack (Control.Monad.Trans.Cont.ContT r m)
+ Control.Monad.Stack.Error: instance Control.Monad.Stack.Error.ErrorStack m => Control.Monad.Stack.Error.ErrorStack (Control.Monad.Trans.Identity.IdentityT m)
+ Control.Monad.Stack.Error: instance Control.Monad.Stack.Error.ErrorStack m => Control.Monad.Stack.Error.ErrorStack (Control.Monad.Trans.Maybe.MaybeT m)
+ Control.Monad.Stack.Error: instance Control.Monad.Stack.Error.ErrorStack m => Control.Monad.Stack.Error.ErrorStack (Control.Monad.Trans.Reader.ReaderT r m)
+ Control.Monad.Stack.Error: instance Control.Monad.Stack.Error.ErrorStack m => Control.Monad.Stack.Error.ErrorStack (Control.Monad.Trans.Select.SelectT r m)
+ Control.Monad.Stack.Error: instance Control.Monad.Stack.Error.ErrorStack m => Control.Monad.Stack.Error.ErrorStack (Control.Monad.Trans.State.Lazy.StateT s m)
+ Control.Monad.Stack.Error: instance Control.Monad.Stack.Error.ErrorStack m => Control.Monad.Stack.Error.ErrorStack (Control.Monad.Trans.State.Strict.StateT s m)
+ Control.Monad.Stack.Error: instance GHC.Base.Monad m => Control.Monad.Stack.Error.ErrorStack (Control.Monad.Trans.Except.ExceptT e m)
+ Control.Monad.Stack.Error: liftError :: ErrorStack m => PopError m a -> m a
+ Control.Monad.Stack.Error: type ErrorConstraints n m = (KnownNat n, StackConstraints n ExceptT ErrorStack m)
+ Control.Monad.Stack.Error: type ErrorDepth n m = IteratePop n ExceptT m
+ Control.Monad.Stack.Error: type MonadErrorDepth n m e = (ErrorConstraints n m, MonadError e (ErrorDepth n m))
+ Control.Monad.Stack.Error: type family PopError m :: * -> *;
+ Control.Monad.Stack.Error: }

Files

ChangeLog.md view
@@ -1,5 +1,10 @@ # Revision history for effect-stack +## 0.3 -- 2019-07-13++* Rename `Control.Monad.Except` to `Control.Monad.Error` for consistency.+* Documentation.+ ## 0.2.1 -- 2019-07-07  * Demand a very new transformers because we import a very new module.
+ Control/Monad/Stack/Error.hs view
@@ -0,0 +1,100 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}++module Control.Monad.Stack.Error where++import Control.Monad.Except+import Control.Monad.Stack.Internal+import Control.Monad.Trans.Accum+import Control.Monad.Trans.Class+import Control.Monad.Trans.Cont+import Control.Monad.Trans.Except+import Control.Monad.Trans.Identity+import Control.Monad.Trans.Maybe+import Control.Monad.Trans.RWS.CPS as RC+import Control.Monad.Trans.RWS.Lazy as RL+import Control.Monad.Trans.RWS.Strict as RS+import Control.Monad.Trans.Reader+import Control.Monad.Trans.Select+import Control.Monad.Trans.State.Lazy as SL+import Control.Monad.Trans.State.Strict as SS+import Control.Monad.Trans.Writer.CPS as WC+import Control.Monad.Trans.Writer.Lazy as WL+import Control.Monad.Trans.Writer.Strict as WS++class Monad m => ErrorStack m where+	type PopError m :: * -> *+	liftError :: PopError m a -> m a++type instance Pop ExceptT m = PopError m+type ErrorDepth n m = IteratePop n ExceptT m+type ErrorConstraints n m = (KnownNat n, StackConstraints n ExceptT ErrorStack m)+type MonadErrorDepth n m e = (ErrorConstraints n m, MonadError e (ErrorDepth n m))++depthError :: forall n m a. ErrorConstraints n m => ErrorDepth n m a -> m a+depthError = depth @n @ExceptT @ErrorStack liftError++instance (ErrorStack m, Monoid w) => ErrorStack (AccumT w m) where+	type PopError (AccumT w m) = PopError m+	liftError = lift . liftError++instance ErrorStack m => ErrorStack (ContT r m) where+	type PopError (ContT r m) = PopError m+	liftError = lift . liftError++instance Monad m => ErrorStack (ExceptT e m) where+	type PopError (ExceptT e m) = m+	liftError = lift++instance ErrorStack m => ErrorStack (IdentityT m) where+	type PopError (IdentityT m) = PopError m+	liftError = lift . liftError++instance ErrorStack m => ErrorStack (MaybeT m) where+	type PopError (MaybeT m) = PopError m+	liftError = lift . liftError++instance (ErrorStack m, Monoid w) => ErrorStack (RC.RWST r w s m) where+	type PopError (RC.RWST r w s m) = PopError m+	liftError = lift . liftError++instance (ErrorStack m, Monoid w) => ErrorStack (RL.RWST r w s m) where+	type PopError (RL.RWST r w s m) = PopError m+	liftError = lift . liftError++instance (ErrorStack m, Monoid w) => ErrorStack (RS.RWST r w s m) where+	type PopError (RS.RWST r w s m) = PopError m+	liftError = lift . liftError++instance ErrorStack m => ErrorStack (ReaderT r m) where+	type PopError (ReaderT r m) = PopError m+	liftError = lift . liftError++instance ErrorStack m => ErrorStack (SelectT r m) where+	type PopError (SelectT r m) = PopError m+	liftError = lift . liftError++instance ErrorStack m => ErrorStack (SL.StateT s m) where+	type PopError (SL.StateT s m) = PopError m+	liftError = lift . liftError++instance ErrorStack m => ErrorStack (SS.StateT s m) where+	type PopError (SS.StateT s m) = PopError m+	liftError = lift . liftError++instance (ErrorStack m, Monoid w) => ErrorStack (WC.WriterT w m) where+	type PopError (WC.WriterT w m) = PopError m+	liftError = lift . liftError++instance (ErrorStack m, Monoid w) => ErrorStack (WL.WriterT w m) where+	type PopError (WL.WriterT w m) = PopError m+	liftError = lift . liftError++instance (ErrorStack m, Monoid w) => ErrorStack (WS.WriterT w m) where+	type PopError (WS.WriterT w m) = PopError m+	liftError = lift . liftError
− Control/Monad/Stack/Except.hs
@@ -1,100 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE TypeFamilies #-}--module Control.Monad.Stack.Except where--import Control.Monad.Except-import Control.Monad.Stack.Internal-import Control.Monad.Trans.Accum-import Control.Monad.Trans.Class-import Control.Monad.Trans.Cont-import Control.Monad.Trans.Except-import Control.Monad.Trans.Identity-import Control.Monad.Trans.Maybe-import Control.Monad.Trans.RWS.CPS as RC-import Control.Monad.Trans.RWS.Lazy as RL-import Control.Monad.Trans.RWS.Strict as RS-import Control.Monad.Trans.Reader-import Control.Monad.Trans.Select-import Control.Monad.Trans.State.Lazy as SL-import Control.Monad.Trans.State.Strict as SS-import Control.Monad.Trans.Writer.CPS as WC-import Control.Monad.Trans.Writer.Lazy as WL-import Control.Monad.Trans.Writer.Strict as WS--class Monad m => ErrorStack m where-	type PopError m :: * -> *-	liftError :: PopError m a -> m a--type instance Pop ExceptT m = PopError m-type ErrorDepth n m = IteratePop n ExceptT m-type ErrorConstraints n m = (KnownNat n, StackConstraints n ExceptT ErrorStack m)-type MonadErrorDepth n m e = (ErrorConstraints n m, MonadError e (ErrorDepth n m))--depthError :: forall n m a. ErrorConstraints n m => ErrorDepth n m a -> m a-depthError = depth @n @ExceptT @ErrorStack liftError--instance (ErrorStack m, Monoid w) => ErrorStack (AccumT w m) where-	type PopError (AccumT w m) = PopError m-	liftError = lift . liftError--instance ErrorStack m => ErrorStack (ContT r m) where-	type PopError (ContT r m) = PopError m-	liftError = lift . liftError--instance Monad m => ErrorStack (ExceptT e m) where-	type PopError (ExceptT e m) = m-	liftError = lift--instance ErrorStack m => ErrorStack (IdentityT m) where-	type PopError (IdentityT m) = PopError m-	liftError = lift . liftError--instance ErrorStack m => ErrorStack (MaybeT m) where-	type PopError (MaybeT m) = PopError m-	liftError = lift . liftError--instance (ErrorStack m, Monoid w) => ErrorStack (RC.RWST r w s m) where-	type PopError (RC.RWST r w s m) = PopError m-	liftError = lift . liftError--instance (ErrorStack m, Monoid w) => ErrorStack (RL.RWST r w s m) where-	type PopError (RL.RWST r w s m) = PopError m-	liftError = lift . liftError--instance (ErrorStack m, Monoid w) => ErrorStack (RS.RWST r w s m) where-	type PopError (RS.RWST r w s m) = PopError m-	liftError = lift . liftError--instance ErrorStack m => ErrorStack (ReaderT r m) where-	type PopError (ReaderT r m) = PopError m-	liftError = lift . liftError--instance ErrorStack m => ErrorStack (SelectT r m) where-	type PopError (SelectT r m) = PopError m-	liftError = lift . liftError--instance ErrorStack m => ErrorStack (SL.StateT s m) where-	type PopError (SL.StateT s m) = PopError m-	liftError = lift . liftError--instance ErrorStack m => ErrorStack (SS.StateT s m) where-	type PopError (SS.StateT s m) = PopError m-	liftError = lift . liftError--instance (ErrorStack m, Monoid w) => ErrorStack (WC.WriterT w m) where-	type PopError (WC.WriterT w m) = PopError m-	liftError = lift . liftError--instance (ErrorStack m, Monoid w) => ErrorStack (WL.WriterT w m) where-	type PopError (WL.WriterT w m) = PopError m-	liftError = lift . liftError--instance (ErrorStack m, Monoid w) => ErrorStack (WS.WriterT w m) where-	type PopError (WS.WriterT w m) = PopError m-	liftError = lift . liftError
+ README.md view
@@ -0,0 +1,448 @@+# Table of Contents++* Table of Contents+* Why?+* Naming conventions+* How do I...+    * ...use the library to write monadic actions?+    * ...use a new transformer with existing kinds of effects?+    * ...create an effect stack for a new kind of effect?+* Why not...+    * ...type-based resolution?+    * ...type-level tags?++# Why?++The `transformers` package gives us a nice library for building up monad+transformer stacks that combine just the right mix of effects for a given+application. For example, in a compiler, we might want to combine `IO` effects+for reading and writing files, a scoping environment effect, the ability to+report warnings and errors, state for generating fresh variable names or+tracking type unification information, and so on. We might cook up a quite+complicated stack like++    type Compiler = ReaderT ScopingInformation+                  ( StateT UnificationState+                  ( ExceptT TypeError+                  ( WriterT [Warning]+                  ( StateT FreshNameGenerator+                  ( IO+                  )))))++to capture all of these effects in one monad.++With just the tools provided by the `transformers` package, though, this type+can be somewhat frustrating to use. Getting access to the `WriterT [Warning]`+part of the stack, for example, involves lifting the write effect through three+layers of the stack using something like:++    warn :: Warning -> Compiler ()+    warn w = lift (lift (lift (tell [w])))++Besides being tedious, this also calcifies the monad stack; if later we+discover we got the stack in the wrong order, or decide we need to add another+effect, we would need to revisit all the places where we did such lifting and+reconsider exactly how many occurrences of `lift` there should be.++The `mtl` package addresses this problem by adding one typeclass for each kind+of effect. (Transformers which don't provide that effect pass it through.) This+gives us two benefits:++1. We can write type signatures that constrict us to using fewer effects than+   our top-level application monad actually provides, and get the compiler to+   check that we have really used only those effects. These actions can still+   be used in the richer top-level application monad.+2. The need for explicitly `lift`ing is drastically reduced, with the typeclass+   resolution mechanism inferring the correct number of `lift`s for us.++For example, using that library, we might write++    warn :: MonadWriter [Warning] m => m ()+    warn w = tell [w]++which now continues to work even if we change the monad stack later, provided+we retain the property of having just one `WriterT` in the stack. The three+uses of `lift` are inferred, and will be adjusted up or down as needed as the+top-level application monad changes.++However, if one wishes to have two or more copies of a single kind of effect,+there is no convenient, generic way to choose anything other than the one that+appears topmost in the stack. With our `Compiler` monad above, for example, we+might write++    unify :: MonadState UnificationState m => Type -> Type -> m ()+    unify t1 t2 = get >>= \us -> ...++to get access to the unification state. But if we want to access the+`FreshNameGenerator`, we are back to writing fragile `lift`-based code:++    freshName :: Compiler Name+    freshName = lift (lift (lift (lift (modify (...)))))++We can, with some effort and perhaps a confusing type signature, retain some of+the benefits of indicating in the type exactly which effects are used by mixing+`mtl`-style actions with `transformers`-style `lift`ing, though I dare say this+style is as yet not very popular:++    freshName :: (MonadTrans t1, MonadTrans t2, MonadState FreshNameGenerator m) => t1 (t2 m) Name+    freshName = lift (lift (modify (...)))++The fragility of `lift` remains, though.++The `effect-stack` package addresses this problem, providing a way to choose+lower layers of the monad stack generically and without explicitly writing the+correct number of `lift`s. It introduces a separate stack for each kind of+effect, and provides an operation for popping one layer of a given effect's+stack. For example, we can still write++    unify :: MonadState UnificationState m => Type -> Type -> m ()++for actions that access the topmost state, but with this library we can also write++    freshName :: (StateStack m, MonadState FreshNameGenerator (PopState m)) => m Name++to access the state from underneath the outermost `StateT`, no matter how deep+it is. We can implement this type using `liftState`; for example:++    freshName = liftState (modify (...))++The typeclass resolution mechanism will turn `liftState` into the correct+number of `lift`s to get from one `StateT` to the next.++Our `Compiler` monad has only two kinds of state, but one could imagine needing+a third. Writing down the type for accessing the third type shows that using the+primitive `StateStack` and `PopState` operations quickly becomes tedious with+deep stacks:++    thirdStateGet :: (StateStack m, StateStack (PopState m), MonadState X (PopState (PopState m))) => m X+    thirdStateGet = liftState (liftState get)++Consequently, the library also provides some type families and operations that+ease this iteration. Using them, we can also write `thirdStateGet` this way:++    thirdStateGet :: MonadStateDepth 2 m X => m X+    thirdStateGet = depthState @2 get++Of course, and unfortunately, inferred types will still use the fully-expanded+form, but at least the human-written types can be a bit prettier.++# Naming conventions++There is one module per kind of effect, named `Control.Monad.Stack.<Effect>`.+Generally, if there is a class for the effect, we drop the initial `Monad` from+the class name and use that as the name of the effect (e.g. `MonadState` &rarr;+`State`). Otherwise we use the final part of the module name from+`transformers` as the effect name (e.g. `Control.Monad.Trans.Accum` &rarr;+`Accum`). Each module exports the following things:++* A typeclass for popping one layer of that kind of effect off the stack at a+  time. This should generally be viewed as a low-level tool, but it may also be+  independently useful.+    * The class is named `<Effect>Stack`.+    * There is an associated type family `Pop<Effect>`; it takes a monad, and+      removes enough transformers to drop the outermost transformer of the+      current kind of effect. For example, `PopState Compiler` would throw away+      the outermost `ReaderT` and `StateT`, leaving a new stack that began at+      the `ExceptT`.+    * There is a method `lift<Effect>`; it applies `lift` the appropriate+      number of times to take an action one layer down in the effect stack and+      lift it to the full monad.+* A type alias `<Effect>Depth`. It takes a type-level number and a monad, and+  calls `Pop<Effect>` the given number of times on the monad. This should+  probably also be considered a low-level tool.+* A type alias `<Effect>Constraints`. It takes a type-level number and a monad,+  and produces a constraint saying that you are permitted to call `Pop<Effect>`+  and `lift<Effect>` the given number of times with the monad. For transformers+  with no associated class, this will likely be the most commonly-used+  type-level export.+* A function `depth<Effect>`. It takes a type-level number and a monadic+  action, and calls `lift<Effect>` the given number of times on the action.+  This will most likely be the most commonly-used computation-level export.++For effects that are associated with a class, the module will also export:++* A type alias `Monad<Effect>Depth`. It takes a type-level number and a monad+  as its first two arguments. For classes which have other parameters than the+  monad, those parameters follow in the same order that the `Monad<Effect>`+  class demands them. (But note that the monad always comes before the other+  arguments, unlike in `mtl`!) It produces a constraint saying that you can+  call `Pop<Effect>` and `lift<Effect>` the given number of times with the+  monad, and that if you call `Pop<Effect>` the given number of times then the+  result is an instance of `Monad<Effect> <args>`. This will likely be the most+  commonly-used type-level export when it is available.++# How do I...++## ...use the library to write monadic actions?++Generally, you will mix `mtl`-style classes for identifying what effect you+want and `effect-stack`-style classes for identifying which layer of your+transformer should provide that effect. In what follows, we will recap the+`Compiler` example from the "Why?" section, including a complete, compilable+file demonstrating simple usage of `effect-stack`. First some imports, data+declarations, and other standard-ish nonsense:++    {-# LANGUAGE DataKinds #-}+    {-# LANGUAGE FlexibleContexts #-}+    {-# LANGUAGE TypeApplications #-}++    -- base+    import System.Exit++    -- mtl+    import Control.Monad.Reader+    import Control.Monad.State+    import Control.Monad.Writer+    import Control.Monad.Except++    -- effect-stack+    import Control.Monad.Stack.State+    import Control.Monad.Stack.Error++    type Compiler = ReaderT ScopingInformation+                  ( StateT UnificationState+                  ( ExceptT TypeError+                  ( WriterT [Warning]+                  ( StateT FreshNameGenerator+                  ( IO+                  )))))++    type Name = String+    type ScopingInformation = [Name]+    type UnificationState = ()+    type Type = ()+    type TypeError = (Type, Type)+    type Warning = String+    type FreshNameGenerator = Int++    runCompiler :: Compiler a -> IO a+    runCompiler act = do+    	(res, warnings) <- evalStateT (runWriterT (runExceptT (evalStateT (runReaderT act []) ()))) 0+    	traverse putStrLn warnings+    	case res of+    		Left err -> die (show err)+    		Right a -> pure a++Now for some actual good stuff. We can still use `mtl`-style polymorphism+freely when there is no ambiguity about which part of the stack should provide+a particular effect. For example, `Compiler` has only one `WriterT`, so there's+no problem knowing which `tell` to use:++    warn :: MonadWriter [Warning] m => Warning -> m ()+    warn w = tell [w]++Similarly, if we want to use the top-level state, we can still use `mtl` if we+want:++    unify :: ( MonadState UnificationState m+             , MonadError TypeError m+             ) => Type -> Type -> m ()+    unify t1 t2 = do+    	us <- get+    	if t1 == t2+    	then put ()+    	else throwError (t1, t2)++Alternately, we can write the same type signature using `effect-stack` types+explicitly saying that we want these effects to be provided by the top-most+transformer that can provide them:++    unify' :: ( MonadStateDepth 0 m UnificationState+              , MonadErrorDepth 0 m TypeError+              ) => Type -> Type -> m ()+    unify' t1 t2 = do+    	us <- get+    	if t1 == t2+    	then put ()+    	else throwError (t1, t2)++Each kind of effect's stack is 0-indexed, so the outermost layer is layer 0. If+we want to access effects not provided by the top-most transformer, then we+must use `effect-stack` types (like `MonadStateDepth`) and methods (like+`depthState`).++    freshName :: MonadStateDepth 1 m FreshNameGenerator => m Name+    freshName = depthState @1 $ do+    	modify (1+)+    	gets show++We can also mix and match, both at the type level (using `mtl`, `base`, and+`effect-stack` classes), and within `do` blocks at the computation level (using+`mtl`-style transformer polymorphic methods, `base`-style polymorphic lifting+methods, and `effect-stack`-style polymorphic lifting methods).++    debug :: ( MonadStateDepth 1 m FreshNameGenerator+             , MonadReader ScopingInformation m+             , MonadIO m+             ) => m ()+    debug = do+    	n <- depthState @1 get+    	env <- ask+    	liftIO (print (n, env))++Here's a `main` that exists just to show that all the pieces can now be+specialized to the `Compiler` type as we wanted:++    main :: IO ()+    main = runCompiler $ do+    	unify () ()+    	warn "PHP is still more popular than Haskell."+    	v <- freshName+    	local (v:) $ do+    		unify' () ()+    		debug++Running it exits successfully after printing++    (1,["1"])+    PHP is still more popular than Haskell.++## ...use a new transformer with existing kinds of effects?++You can write new instances for the existing effect stack classes for your+transformer. You must first decide whether the transformer you are writing an+instance for provides the effect the class provides a stack for or not. For+example, for the `StateStack` class, does your transformer provide access to+some kind of stateful effect?++If it does, write an instance in which the `Pop<Effect>` family immediately+returns the monad being transformed, and `lift<Effect>` is just `lift`. For+example, because the `AccumT` family of transformers provides the+`Accum`ulation effect, the library provides this instance:++    instance (Monad m, Monoid w) => AccumStack (AccumT w m) where+    	type PopAccum (AccumT w m) = m+    	liftAccum = lift++The `(Monad m, Monoid w)` constraints are needed to satisfy the `Monad`+superclass of `AccumStack`; all the `<Effect>Stack` classes have this+superclass for user convenience.++If your transformer does not provide the effect, you should write an instance+that passes everything down one layer: `Pop<Effect>` should recurse on the+transformed monad, and `lift<Effect>` should be `lift . lift<Effect>`. For+example, since `MaybeT` does not provide `Accum`ulation effects, the library+provides this instance:++    instance AccumStack m => AccumStack (MaybeT m) where+    	type PopAccum (MaybeT m) = PopAccum m+    	liftAccum = lift . liftAccum++## ...create an effect stack for a new kind of effect?++You will want to create a new class for the effect that provides the low-level+tools for popping one layer of the stack at a time at the type level and+lifting one layer at a time at the computation level. Once you have done that,+there are some tools in `Control.Monad.Stack.Internal` that will be helpful for+creating the high-level interface that accepts type-level numbers and iterates+the low-level operations.++The classes are all quite similar to each other; you should be able to follow+the exact same pattern for each new effect. The class itself should look like+this:++    class Monad m => <Effect>Stack m where+    	type Pop<Effect> m :: * -> *+    	lift<Effect> :: Pop<Effect> m a -> m a++At this point you will need to choose a type-level token that can uniquely+identify this kind of effect. The type families that need this token as an+argument are poly-kinded and will accept a token of any kind here. For most of+the effects in this library, the token was chosen to be one of the transformers+that is typically used to provide the effect. You may also simply invent a new+type without exporting it if you are paranoid about collisions. Once you have+chosen a token, make a mapping from the token to the family created above:++    type instance Pop <Token> m = Pop<Effect> m++This token may now be passed to `IteratePop`, `StackConstraints`, and `depth`+to provide the high-level interface:++    type <Effect>Depth n m = IteratePop n <Token> m+    type <Effect>Constraints n m = (KnownNat n, StackConstraints n <Token> <Effect>Stack m)++    depth<Effect> :: forall n m a. <Effect>Constraints n m a => <Effect>Depth n m a -> m a+    depth<Effect> = depth @n @<Token> @<Effect>Stack lift<Effect>++If there is a class associated with the effect, you may also want to offer a+suitably stackified version of that class:++    type Monad<Effect>Depth n m <args> =+    	( <Effect>Constraints n m+    	, Monad<Effect> <args> (<Effect>Depth n m)+    	)++You will probably also want to write a bunch of instances for existing+transformers. See the section "How do I...", subsection "...use a new+transformer with existing kinds of effects?" for more information on doing+this.++# Why not...++## ...type-based resolution?++One alternate method of selecting which of many copies of an effect to use from+a stack would be to look at the type being used for that effect. For example,+continuing the `Compiler` example form the "Why?" section, one might imagine+that one could write a class which used type inference to decide whether the+current stateful actions were mucking about with `FreshNameGenerator`s or+`UnificationState`s, and use that information to decide whether to `lift` once+or four times.++This approach has two main drawbacks:++1. It turns out that type inference fails to differentiate between the two+   situations surprisingly often. This puts an unusually high type-annotation+   burden on users. (Indeed, this is the standard justification for the+   functional dependency included in all `mtl` typeclasses.)+2. It still leaves you open to the problem of mixing effects which just happen,+   by coincidence, to need access to the same type in the effect. For example,+   suppose your compiler is tracking how many tab characters it has seen so+   that it can issue an appropriate warning. Even so, some local module might+   want to slap a transformer on top to add an effect for tracking the arity of+   the function currently being compiled. These both happen to be `Int`s, and+   so once again we have a disambiguation problem.++## ...type-level tags?++One could imagine adding a tag to each transformer, and using the tags to+differentiate which effect is wanted. For example, with suitably modified+transformers, one might write:++    data Tag = Unification | Fresh | Other+    type Compiler = ReaderT Other ScopingInformation+                  ( StateT Unification UnificationState+                  ( ExceptT Other TypeError+                  ( WriterT Other [Warning]+                  ( StateT Fresh FreshNameGenerator+                  ( IO+                  )))))++Then, instead of using type-level numbers to indicate the depth in a stack, one+would use the tag to indicate which part of the stack was meant; so one might+imagine writing something like:++    unify :: MonadState Unification UnificationState m => Type -> Type -> m ()+    unify t1 t2 = get @Unification >>= \us -> ...++    freshName :: MonadState Fresh FreshNameGenerator m => m Name+    freshName = modify @Fresh (...)++Convenient shorthands could be provided by the appropriate libraries for+selecting, say, the `()` tag by default when the stack of interest was+unambiguous about which layer should provide a given effect.++Unlike choosing by effect or choosing by effect+type, one need not worry about+collisions with tags; modules which want to transform an existing monad could+ensure they use fresh tags by just making a new data kind.++This approach has a lot going for it, and I'd love to see a competing library+attempt this. The main drawback is that it is all-or-nothing, in that the+existing `transformers` transformers do not have these tags. By contrast,+`effect-stack` interoperates smoothly with existing `transformers` stacks. This+means that++1. Existing projects can adopt this library without a big migration.+2. New projects can use just `transformers`+`mtl`, which are syntactically and+   conceptually very light, right up to the moment that they need something+   more complicated.
effect-stack.cabal view
@@ -1,57 +1,35 @@ name:                effect-stack-version:             0.2.1+version:             0.3 synopsis:            Reducing the pain of transformer stacks with duplicated effects-description:         When using monad transformer stacks, it is common to want-                     to mix effects from various layers of the stack within a-                     single block of code. The @lift@ operation can be used to-                     convert an action that uses effects at some deep layer of-                     the stack into one that works in the full stack. It-                     quickly becomes tedious to include exactly the right-                     number of calls to @lift@ each time they are needed; and-                     makes the code more fragile when the transformer stack is-                     changed (e.g. to include a new effect).-                     .-                     The @mtl@ package provides a convenient way to point to a-                     particular layer of the stack, under the assumption that-                     there is exactly one "kind" of each interesting effect.-                     (For example, one can only have one type of state, one-                     type of environment to read from, and so forth.) However,-                     if one wishes to have to copies of a single kind of-                     effect, there is no convenient, generic way to choose-                     anything other than the one that appears topmost in the-                     stack. For example, for a stack that contains two-                     @StateT@s in it, one can write code that accesses the-                     outermost state using a type like-                     .-                     @MonadState outer m => m ()@-                     .-                     but there is no polymorphic way to reach the inner-                     @StateT@'s state. One is back to writing fragile code that-                     depends on exactly which transformer stack was chosen.-                     .-                     This package provides a way to make such choices-                     generically: it introduces a separate stack for each kind-                     of effect, and provides an operation for popping one layer-                     of a given effect's stack. Continuing the @StateT@-                     example, one could write+description:         The @mtl@ provides a nice way to write monadic actions+                     which take advantage of a particular kind of effect (say,+                     statefulness or exception handling) without being forced+                     to say exactly which monad is providing that effect.+                     However, if a transformer stack includes two transformers+                     that provide the given effect, @mtl@ does not provide a+                     clean way to disambiguate which one is wanted; the topmost+                     one is always chosen.                      .-                     @MonadState outer m => m ()@+                     This package provides tools for disambiguating without+                     being forced to choose a particular transformer stack. It+                     provides a separate stack for each kind of effect; you may+                     then disambiguate by depth within each stack. For example,+                     in a stack with two @StateT@ transformers, one can write                      .-                     as before for the outermost state, or+                     @foo :: MonadStateDepth 0 m a => m a+                     foo = depthState \@0 get@                      .-                     @(StateStack m, MonadState inner (PopState m)) => m ()@+                     for access to the topmost state effects, or                      .-                     to access the state from underneath the outermost-                     @StateT@, no matter how deep it is. A sample action of-                     that type would be @liftState get >> return ()@.-                     Equivalently, there is-                     some mild sugar that lets you write the type+                     @bar :: MonadStateDepth 1 m a => m a+                     bar = depthState \@1 get@                      .-                     @MonadStateDepth 1 m inner => m ()@+                     for access to the state from underneath the topmost+                     @StateT@, no matter how deep in the stack the two+                     @StateT@s are.                      .-                     to mean the same thing as the previous type, and-                     @depthState \@1 get >> return ()@ to mean the same thing-                     as the previous action.+                     See the readme for more detailed motivation, usage+                     examples, and documentation. license:             BSD3 license-file:        LICENSE author:              Daniel Wagner@@ -59,7 +37,7 @@ -- copyright: category:            Control build-type:          Simple-extra-source-files:  ChangeLog.md+extra-source-files:  ChangeLog.md README.md cabal-version:       2.0  source-repository head@@ -70,7 +48,7 @@   exposed-modules:                        Control.Monad.Stack.Accum,                        Control.Monad.Stack.Cont,-                       Control.Monad.Stack.Except,+                       Control.Monad.Stack.Error,                        Control.Monad.Stack.Fail,                        Control.Monad.Stack.Internal,                        Control.Monad.Stack.Reader,