|
155 | 155 |
|
156 | 156 | The types of the arguments for the functions are the same as for (server-side) request handlers.
|
157 | 157 |
|
| 158 | +## Changing the monad the client functions live in |
| 159 | +
|
| 160 | +Just like `hoistServer` allows us to change the monad in which request handlers |
| 161 | +of a web application live in, we also have `hoistClient` for changing the monad |
| 162 | +in which _client functions_ live. Consider the following trivial API: |
| 163 | +
|
| 164 | +``` haskell |
| 165 | +type HoistClientAPI = Get '[JSON] Int :<|> Capture "n" Int :> Post '[JSON] Int |
| 166 | +
|
| 167 | +hoistClientAPI :: Proxy HoistClientAPI |
| 168 | +hoistClientAPI = Proxy |
| 169 | +``` |
| 170 | +
|
| 171 | +We already know how to derive client functions for this API, and as we have |
| 172 | +seen above they all return results in the `ClientM` monad when using `servant-client`. |
| 173 | +However, `ClientM` rarely (or never) is the actual monad we need to use the client |
| 174 | +functions in. Sometimes we need to run them in IO, sometimes in a custom monad |
| 175 | +stack. `hoistClient` is a very simple solution to the problem of "changing" the monad |
| 176 | +the clients run in. |
| 177 | +
|
| 178 | +``` haskell ignore |
| 179 | +hoistClient |
| 180 | + :: HasClient ClientM api -- we need a valid API |
| 181 | + => Proxy api -- a Proxy to the API type |
| 182 | + -> (forall a. m a -> n a) -- a "monad conversion function" (natural transformation) |
| 183 | + -> Client m api -- clients in the source monad |
| 184 | + -> Client n api -- result: clients in the target monad |
| 185 | +``` |
| 186 | +
|
| 187 | +The "conversion function" argument above, just like the ones given to `hoistServer`, must |
| 188 | +be able to turn an `m a` into an `n a` for any choice of type `a`. |
| 189 | +
|
| 190 | +Let's see this in action on our example. We first derive our client functions as usual, |
| 191 | +with all of them returning a result in `ClientM`. |
| 192 | +
|
| 193 | +``` haskell |
| 194 | +getIntClientM :: ClientM Int |
| 195 | +postIntClientM :: Int -> ClientM Int |
| 196 | +getIntClientM :<|> postIntClientM = client hoistClientAPI |
| 197 | +``` |
| 198 | +
|
| 199 | +And we finally decide that we want the handlers to run in IO instead, by |
| 200 | +"post-applying" `runClientM` to a fixed client environment. |
| 201 | +
|
| 202 | +``` haskell |
| 203 | +-- our conversion function has type: forall a. ClientM a -> IO a |
| 204 | +-- the result has type: |
| 205 | +-- Client IO HoistClientAPI = IO Int :<|> (Int -> IO Int) |
| 206 | +getClients :: ClientEnv -> Client IO HoistClientAPI |
| 207 | +getClients clientEnv |
| 208 | + = hoistClient hoistClientAPI |
| 209 | + ( fmap (either (error . show) id) |
| 210 | + . flip runClientM clientEnv |
| 211 | + ) |
| 212 | + (client hoistClientAPI) |
| 213 | +``` |
| 214 | +
|
158 | 215 | ## Querying Streaming APIs.
|
159 | 216 |
|
160 | 217 | Consider the following streaming API type:
|
|
0 commit comments