Skip to content

Commit fc3c608

Browse files
committed
document hoistClient (haddocks, tutorial)
1 parent 9eb57a6 commit fc3c608

File tree

2 files changed

+70
-1
lines changed

2 files changed

+70
-1
lines changed

doc/tutorial/Client.lhs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,63 @@ Email {from = "[email protected]", to = "[email protected]", subject = "Hey Alp, we mi
155155
156156
The types of the arguments for the functions are the same as for (server-side) request handlers.
157157
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+
158215
## Querying Streaming APIs.
159216
160217
Consider the following streaming API type:

servant-client/src/Servant/Client/Internal/HttpClient.hs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,19 @@ client :: HasClient ClientM api => Proxy api -> Client ClientM api
7272
client api = api `clientIn` (Proxy :: Proxy ClientM)
7373

7474
-- | Change the monad the client functions live in, by
75-
-- supplying a natural transformation.
75+
-- supplying a conversion function
76+
-- (a natural transformation to be precise).
77+
--
78+
-- For example, assuming you have some @manager :: 'Manager'@ and
79+
-- @baseurl :: 'BaseUrl'@ around:
80+
--
81+
-- > type API = Get '[JSON] Int :<|> Capture "n" Int :> Post '[JSON] Int
82+
-- > api :: Proxy API
83+
-- > api = Proxy
84+
-- > getInt :: IO Int
85+
-- > postInt :: Int -> IO Int
86+
-- > getInt :<|> postInt = hoistClient api (flip runClientM cenv) (client api)
87+
-- > where cenv = mkClientEnv manager baseurl
7688
hoistClient
7789
:: HasClient ClientM api
7890
=> Proxy api

0 commit comments

Comments
 (0)