|
| 1 | +--- |
| 2 | +title: Monad Transformers |
| 3 | +author: Michael Snoyman <michael@fpcomplete.com> |
| 4 | +description: What transformers are, why they're useful, what to be aware of |
| 5 | +first-written: 2015-02-24 |
| 6 | +last-updated: 2015-02-24 |
| 7 | +last-reviewed: 2015-02-24 |
| 8 | +--- |
| 9 | + |
| 10 | +# Monad Transformers |
| 11 | + |
| 12 | +## Basic Transformers |
| 13 | + |
| 14 | +The following is a list of some basic transformers: |
| 15 | + |
| 16 | +### MaybeT |
| 17 | + |
| 18 | +A `Maybe a` wrapped in any other monad, i.e. `m (Maybe a)` |
| 19 | + |
| 20 | +### ReaderT |
| 21 | + |
| 22 | +A `Reader r a` in which the resulting `a` is wrapped in any other monad, i.e. `r -> m a` |
| 23 | + |
| 24 | +### StateT |
| 25 | + |
| 26 | +A `State s a` in which the return value and state, namely `(a, s)`, are wrapped in any other monad, i.e. `s -> m (a, s)` |
| 27 | + |
| 28 | +### ExceptT |
| 29 | + |
| 30 | +An `Either e a` wrapped in any other monad, i.e. `m (Either e a)` |
| 31 | + |
| 32 | +## Simple examples of usage |
| 33 | + |
| 34 | +[transformers](https://www.stackage.org/package/transformers) is a widely used package which provides transformer versions of various monads. It also provides two useful classes, `MonadTrans` and `MonadIO`. |
| 35 | + |
| 36 | +Instances of `MonadTrans` are transformers which can be applied to other monads to create new monads. All of the transformers defined in the `transformers` package are instances of `MonadTrans`. |
| 37 | + |
| 38 | +### MonadTrans |
| 39 | + |
| 40 | +`MonadTrans` defines one method, `lift`, the signature of which is |
| 41 | + |
| 42 | +```haskell |
| 43 | +lift :: Monad m => m a -> t m a |
| 44 | +``` |
| 45 | + |
| 46 | +Given a monad `m`, we can "lift" into a constructed monad transformer `t` so long as `t` is an instance of `MonadTrans` |
| 47 | + |
| 48 | +### MonadIO |
| 49 | + |
| 50 | +`MonadIO` defines one method, `liftIO`, the signature of which is |
| 51 | + |
| 52 | +```haskell |
| 53 | +liftIO :: IO a -> m a |
| 54 | +``` |
| 55 | + |
| 56 | +`liftIO` allows us to lift an IO action into a transformer stack that is built on top of IO and it works no matter how deeply nested the stack is. We'll see some examples of this below. |
| 57 | + |
| 58 | + |
| 59 | +Examples: |
| 60 | + |
| 61 | +### MaybeT |
| 62 | + |
| 63 | +```haskell |
| 64 | +import Control.Monad |
| 65 | +import Control.Monad.Trans.Maybe |
| 66 | +import Control.Monad.Trans.Class |
| 67 | + |
| 68 | +main = do |
| 69 | + password <- runMaybeT getPassword |
| 70 | + case password of |
| 71 | + Just p -> putStrLn "valid password!" |
| 72 | + Nothing -> putStrLn "invalid password!" |
| 73 | + |
| 74 | +isValid :: String -> Bool |
| 75 | +isValid = (>= 10) . length |
| 76 | + |
| 77 | +getPassword :: MaybeT IO String |
| 78 | +getPassword = do |
| 79 | + password <- lift getLine |
| 80 | + guard (isValid password) |
| 81 | + return password |
| 82 | +``` |
| 83 | + |
| 84 | +In this example, we combine the `IO` and `Maybe` monads. `lift getLine` allows us to embed the `IO` action into the `MaybeT` transformer, yielding a value of type `MaybeT IO String`. |
| 85 | + |
| 86 | +Note that in this particular example, the use of `lift` in `lift getLine` is equivalent to `liftIO getLine` since we have a one-layer transformer on top of `IO`. |
| 87 | + |
| 88 | +### lift vs liftIO |
| 89 | + |
| 90 | +Here's a (somewhat contrived) example that demonstrates the difference between `lift` and `liftIO` and the usefulness of the latter. |
| 91 | + |
| 92 | +Suppose we added another layer to our transformer stack so that, instead of `MaybeT IO String`, we had `MaybeT (ExceptT MyPasswordError IO) String`. |
| 93 | + |
| 94 | +As in our first example, we'd like to lift the `getLine` action into our transformer. Let's try. |
| 95 | + |
| 96 | +```haskell |
| 97 | +getPassword' :: MaybeT (ExceptT MyPasswordError IO) String |
| 98 | +getPassword' = do |
| 99 | + password <- lift getLine |
| 100 | + guard (isValid password) |
| 101 | + return password |
| 102 | +``` |
| 103 | + |
| 104 | +We get an error. Oops! |
| 105 | + |
| 106 | +``` |
| 107 | +Couldn't match type ‘IO’ with ‘ExceptT MyPasswordError IO’ |
| 108 | +Expected type: ExceptT MyPasswordError IO String |
| 109 | + Actual type: IO String |
| 110 | +In the first argument of ‘lift’, namely ‘getLine’ |
| 111 | +In a stmt of a 'do' block: password <- lift getLine |
| 112 | +``` |
| 113 | + |
| 114 | +If we look at the type of `lift` when specialized to various transformers, we can see the problem. |
| 115 | + |
| 116 | +``` |
| 117 | +> :t \x -> (lift x :: MaybeT IO String) |
| 118 | +\x -> (lift x :: MaybeT IO String) :: IO String -> MaybeT IO String |
| 119 | +``` |
| 120 | + |
| 121 | +In this example, we can use `lift` to go from `IO` into our transformer. But with a deeper stack, we run into problems: |
| 122 | + |
| 123 | +``` |
| 124 | +> type MyDeeperStack = ReaderT Int (WriterT String IO) Bool |
| 125 | +> :t \x -> (lift x :: MyDeeperStack) |
| 126 | +\x -> (lift x :: MyDeeperStack) |
| 127 | + :: WriterT String IO Bool -> MyDeeperStack |
| 128 | +``` |
| 129 | + |
| 130 | +In other words, the `m` from `lift :: m a -> t m a` in our `MyDeeperStack` is `WriterT String IO`. So we would to need `lift` *again* in order to go from `IO Bool -> MyDeeperStack`, i.e. |
| 131 | + |
| 132 | +``` |
| 133 | +> :t \x -> ((lift . lift) x :: MyDeeperStack) |
| 134 | +\x -> ((lift . lift) x :: MyDeeperStack) |
| 135 | + :: IO Bool -> MyDeeperStack |
| 136 | +``` |
| 137 | + |
| 138 | +This is where `liftIO` helps us. It essentially lets us do a variable number of lifts. This lets us write less brittle code because if we decided to add yet another layer to our transformer stack, we wouldn't have to hardcode another call to `lift`. |
| 139 | + |
| 140 | +As an example, what happens if we add a `MaybeT` to our stack? |
| 141 | + |
| 142 | +```haskell |
| 143 | +type MyDeeperStack = ReaderT Int (WriterT String (MaybeT IO)) Bool |
| 144 | +``` |
| 145 | + |
| 146 | +`lift . lift` will no longer allow us to lift an `IO` action into our stack because we now have a third layer. |
| 147 | + |
| 148 | +``` |
| 149 | +> :t \x -> ((lift . lift) x :: MyDeeperStack) |
| 150 | +\x -> ((lift . lift) x :: MyDeeperStack) |
| 151 | + :: MaybeT IO Bool -> MyDeeperStack |
| 152 | +``` |
| 153 | +
|
| 154 | +With `liftIO`, all is well: |
| 155 | +
|
| 156 | +``` |
| 157 | +> :t \x -> (liftIO x :: MyDeeperStack) |
| 158 | +\x -> (liftIO x :: MyDeeperStack) :: IO Bool -> MyDeeperStack |
| 159 | +``` |
| 160 | +
|
| 161 | +Want to add another layer? No problem: |
| 162 | +
|
| 163 | +```haskell |
| 164 | +type MyDeeperStack = ReaderT Int (WriterT String (MaybeT (ExceptT String IO))) Bool |
| 165 | +``` |
| 166 | + |
| 167 | +``` |
| 168 | +> :t \x -> (liftIO x :: MyDeeperStack) |
| 169 | +\x -> (liftIO x :: MyDeeperStack) :: IO Bool -> MyDeeperStack |
| 170 | +``` |
| 171 | + |
| 172 | +Without `liftIO` we'd need to keep adjusting the number of lifts: |
| 173 | + |
| 174 | +``` |
| 175 | +> :t \x -> ((lift . lift . lift . lift) x :: MyDeeperStack) |
| 176 | +\x -> ((lift . lift . lift . lift) x :: MyDeeperStack) |
| 177 | + :: IO Bool -> MyDeeperStack |
| 178 | +``` |
| 179 | + |
| 180 | +* More transformer usage examples |
| 181 | +* Pitfalls of Writer laziness |
| 182 | +* Dealing with exceptions and control structures (monad-control and exceptions packages), and losing state |
| 183 | +* Monad transformers: [EitherT vs IO](http://stackoverflow.com/questions/25752900/exceptions-and-monad-transformers/25753497#25753497) |
| 184 | +* https://github.com/kqr/gists/blob/master/articles/gentle-introduction-monad-transformers.md (need to get permission to relicense) |
0 commit comments