git.iko.soy/typeable/servant
on master help sign in
document hoistClient (haddocks, tutorial)
commitfc3c6089b8bb210a0e867c2535293f715abb5e77 authorAlp Mestanogullari <alpmestan@gmail.com> dateWed 4 Apr 2018, 01:48 (9 years ago) parent9eb57a6
2 files changed+70−1
doc/tutorial/Client.lhs+57 servant-client/src/Servant/Client/Internal/HttpClient.hs+13−1
doc/tutorial/Client.lhs+57
@@ -155,6 +155,63 @@ Email {from = "great@company.com", to = "alp@foo.com", subject = "Hey Alp, we mi
155155
156156The 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
160217Consider the following streaming API type:
servant-client/src/Servant/Client/Internal/HttpClient.hs+13−1
@@ -72,7 +72,19 @@ client :: HasClient ClientM api => Proxy api -> Client ClientM api
7272client 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
7688hoistClient
7789 :: HasClient ClientM api
7890 => Proxy api