[Snapshot of ch17 Bryan O'Sullivan **20080420010106] { addfile ./examples/ch17/StackStack.hs hunk ./en/ch17-monad-trans.xml 221 - function we developed earlier. We use ReaderT to - store configuration data, in the form of the maximum depth of - recursion we will perform. We also use StateT to - record the maximum depth we reach during an actual - traversal. + function we developed earlier. We will modify it to recurse no + deeper into a directory tree than a given amount, and to record + the maximum depth it reaches. + + &UglyStack.hs:AppData; + + We use ReaderT to store configuration data, in + the form of the maximum depth of recursion we will perform. We + also use StateT to record the maximum depth we + reach during an actual traversal. hunk ./en/ch17-monad-trans.xml 240 + + Where's the missing type parameter? + + You might have noticed that our &type; synonym doesn't + have the usual type parameter a that we + associate with a monadic type: + + &UglyStack.hs:App2; + + Both App and App2 work fine in + normal type signatures. The difference arises when we try to + construct another type from one of these. Say we want to add + another monad transformer to the stack: the compiler will + allow WriterT [String] App a, but reject + WriterT [String] App2 a. + + The reason for this is that Haskell does not allow us to + partially apply a type synonym. The synonym App + doesn't take a type parameter, so it doesn't pose a problem. + However, because App2 takes a type parameter, we + must supply some type for that parameter if we want to use + App2 to create another type. + + This restriction is limited to type synonyms. When we + create a monad transformer stack, we usually wrap it with a + &newtype; (as we will see below). As a result, we will rarely run into + this problem in practice. + + hunk ./en/ch17-monad-trans.xml 375 - Sometimes, it is desirable to access the underlying monad in - a generic way: we need a monad, but don't care which kind. The - MonadTrans type class defines a - lift function for exactly this - situation. + Instead of relying on all of these type class instances to + work for us behind the scenes, we can be explicit. The + MonadTrans type class defines a useful function + named lift. hunk ./en/ch17-monad-trans.xml 383 - stack, and turns it into an action in the current monad - transformer. + stack, and turns it&emdash;in other words, + lifts it&emdash;into an action in the + current monad transformer. Every monad transformer is an + instance of MonadTrans. + + Let's revisit the App monad stack we defined + earlier (before we wrapped it with a &newtype;). + + &UglyStack.hs:App.noid; + + If we want to access the AppState carried by + the StateT, we would usually rely on + mtl's type classes and instances to handle the + plumbing for us. + + &UglyStack.hs:implicitGet; + + The lift function lets us achieve the + same effect, by lifting get from + StateT into ReaderT. + + &UglyStack.hs:explicitGet; + + Obviously, when we can let mtl do this work for + us, we end up with cleaner code, but this is not always + possible. + + For instance, we can create a monad transformer stack in + which instances of the same type class appear at different + levels. + + &StackStack.hs:Foo; + + If we try to use MonadState's + put action, StateT Int's + instance of MonadState is the one we get, because + it's at the top of the stack. + + &StackStack.hs:outerPut; + + To access State's put, we + must use lift. + + &StackStack.hs:innerPut; + + Sometimes, we need to access a monad more than one level + down the stack, in which case we must compose calls to + lift. + + &StackStack.hs:Bar; + + When we use lift, this tends to make + our code somewhat ugly. Fortunately, the need does not often + arise in practice. When we can't avoid + lift, it's usually tidiest to write wrapper + functions that do the lifting for us, as we have shown above. + Our regular code then uses the wrappers, instead of a sprinkling + of explicit lifts. hunk ./examples/ch17/StackStack.hs 1 +import Control.Monad.State +import Control.Monad.Reader + +{-- snippet Foo --} +type Foo = StateT Int (State String) +{-- /snippet Foo --} + +{-- snippet outerPut --} +outerPut :: Int -> Foo () +outerPut = put +{-- /snippet outerPut --} + +{-- snippet innerPut --} +innerPut :: String -> Foo () +innerPut s = lift (put s) +{-- /snippet innerPut --} + +{-- snippet Bar --} +type Bar = ReaderT Bool Foo + +barPut :: String -> Bar () +barPut = lift . lift . put +{-- /snippet Bar --} hunk ./examples/ch17/UglyStack.hs 7 -{-- snippet App --} +{-- snippet AppData --} hunk ./examples/ch17/UglyStack.hs 20 +{-- /snippet AppData --} hunk ./examples/ch17/UglyStack.hs 22 -type App a = ReaderT AppConfig (StateT AppState IO) a +{-- snippet App --} +type App = ReaderT AppConfig (StateT AppState IO) hunk ./examples/ch17/UglyStack.hs 26 +{-- snippet App2 --} +type App2 a = ReaderT AppConfig (StateT AppState IO) a +{-- /snippet App2 --} + hunk ./examples/ch17/UglyStack.hs 70 +{-- snippet explicitGet --} +explicitGet :: App AppState +explicitGet = lift get +{-- /snippet explicitGet --} + +{-- snippet implicitGet --} +implicitGet :: App AppState +implicitGet = get +{-- /snippet implicitGet --} + }