You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: content/monad-transformers.md
+65-6Lines changed: 65 additions & 6 deletions
Original file line number
Diff line number
Diff line change
@@ -33,7 +33,7 @@ An `Either e a` wrapped in any other monad, i.e. `m (Either e a)`
33
33
34
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
35
36
-
`MonadTrans`makes it easy to embed one monad into another. All of the transformers defined in the `transformers` package are instances `MonadTrans`.
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
37
38
38
### MonadTrans
39
39
@@ -89,17 +89,19 @@ Note that in this particular example, the use of `lift` in `lift getLine` is equ
89
89
90
90
Here's a (somewhat contrived) example that demonstrates the difference between `lift` and `liftIO` and the usefulness of the latter.
91
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.
In this variation we have more than one layer on top of `IO`. We have a `MaybeT` on top of `ExceptT` on top of `IO`. This is where `liftIO` helps us. We can use use `liftIO` to lift the `getLine` action into our stack no matter how deep `IO` is in our stack.
101
-
102
-
If we tried to use `lift` instead of `liftIO`, we'd see the following error:
104
+
We get an error. Oops!
103
105
104
106
```
105
107
Couldn't match type ‘IO’ with ‘ExceptT MyPasswordError IO’
@@ -109,8 +111,65 @@ In the first argument of ‘lift’, namely ‘getLine’
109
111
In a stmt of a 'do' block: password <- lift getLine
110
112
```
111
113
112
-
The error means we have another layer of our stack that we need to traverse before we can lift the IO action into our stack. In other words, we would need to do `lift (lift getLine)`. This is precisely what `liftIO` gives us. Doing `lift . lift . lift ...` is unmaintainable because it relies on the stack being a specific depth. If we decided to add another monad to our stack, our nested lifting would break. With `liftIO` we can short circuit this and simply lift the IO action all the way to the bottom of our stack.
114
+
If we look at the type of `lift` when specialized to various transformers, we can see the problem.
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
+
```> type MyDeeperStack = ReaderT Int (WriterT String IO) Bool
124
+
> :t \x -> (lift x :: MyDeeperStack)
125
+
\x -> (lift x :: MyDeeperStack)
126
+
:: WriterT String IO Bool -> MyDeeperStack
127
+
```
128
+
129
+
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.
130
+
131
+
```> :t \x -> ((lift . lift) x :: MyDeeperStack)
132
+
\x -> ((lift . lift) x :: MyDeeperStack)
133
+
:: IO Bool -> MyDeeperStack
134
+
```
135
+
136
+
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`.
137
+
138
+
As an example, what happens if we add a `MaybeT` to our stack?
0 commit comments