There are two answers, which depend on whether or not you want to block
on the request to an external service.
Scenario A) You want to block waiting for a response
The solution for this is that the next generalization of `mvc` will
replace the `State` layer of the model with a free monad of all possible
synchronous interactions. See this post of mine, which explains the
general concept behind this:
http://www.haskellforall.com/2012/07/purify-code-using-free-monads.html
The intended use case for this is interactions with a database (which
are supposed to be fast and synchronous). However, HTTP requests don't
really seem appropriate for this scenario since they usually have
non-trivial latency and you will freeze your entire web service if you
block waiting for a request.
Scenario B) You want to asynchronously wait for a response
The solution for this is to continue to use a `View` and `Controller` to
interact with the external service. This forces you to structure your
model so that it doesn't block waiting for the response.
However, it sounds like this would benefit from layering an orthogonal
abstraction on top of `mvc` that would let you write code that looks
like concurrent synchronous requests but gets compiled to the correct
single-threaded formulation in terms of `Model`s, `View`s, and
`Controller`s. The rough outline I'm thinking of is something like:
-- The base functor for an external request
newtype Remote req resp x = Remote (req, respond -> x) deriving
(Functor)
-- A synonym for `Data.Functor.CoProduct`(the sum of two functors)
type a :++: b = Data.Functor.Coproduct.CoProduct a b
-- The empty functor, analogous to `Void`
data VoidF a
-- A synonym for `Either`
type a :+: b = Either a b
-- See below for how to generalize this to an arbitrary number
-- of external services using something like type families
serve
:: (reqLocal -> Free (Remote reqR1 respR1 :++: Remote reqR2 respR2 :++:
VoidF) respLocal)
-> Model (reqLocal :+: respR1 :+: respR2 :+: Void) (respLocal
:+: reqR1 :+: reqR2 :+: Void)
The argument of `serve` would be the handler for each incoming request.
It takes an argument of type `reqLocal` (i.e. "request local") and makes
a few calls to two external services, and then returns a result of type
`respLocal` when it is done handling the request. Then `serve` takes
that handler and compiles it to the equivalent `Model`.
Generalizing this type signature to any number of services should be
possible, but I need to think about it some more. The more general
signature might look like this:
serve
:: (reqLocal -> Free remote respLocal)
-> Model (reqLocal :+: OutputsOf remote) (respLocal :+:
InputsOf remote)
... where `OutputsOf` and `InputsOf` are type level functions from the
coproduct of services to sums of outputs or inputs, respectively:
type OutputsOf (Remote reqR respR :++: x) = respR :+: OutputsOf x
type OutputsOf VoidF = Void
type InputsOf (Remote reqR respR :++: x) = reqR :+: InputsOf x
type InputsOf VoidF = Void
Yeah, I think that would work. Give me some time and I will try my hand
at that and see if I can implement something like `serve`. Then ideally
you would be able to just write something like this:
let model = serve $ \incoming -> do
...
resp1 <- external1 req1
...
resp2 <- external2 req2
...
return outgoing
You'd still be responsible for providing the appropriate `View`s and
`Controller`s, but the hard part of chaining "synchronous" requests
together without blocking your `Model` would be done for you and you'd
still preserve the purity of the `Model`.
In fact, I wonder if I can use this to simultaneously solve the database
interface, too, without having to modify `mvc` at all. This seems like a
much more general solution than I anticipated when I first started
writing out this e-mail.
On 4/30/14, 11:03 AM, Kai Wang wrote:
From the documentation, the "Controller" represents an input to the
model, and the "View" is an output.
In the context of an RESTful web app, I would map an processed HTTP
request (GET/POST/etc) to Controller,
and rendered HTML/JSON as view.
However, real life web app would need much more than that. Often they
need additional IO. Say I need to make
use of some 3rd party web service. Then I would need to send some http
request. This request I send is
an output of my model, but I can hardly call it a "View". How would I
deal with situations like this with mvc
library? Other additional IOs like logging, state persistence also
don't fit into the mvc concept very well.
How can we add these features?
--
You received this message because you are subscribed to the Google
Groups "Haskell Pipes" group.
To unsubscribe from this group and stop receiving emails from it, send
an email to [email protected]
<mailto:[email protected]>.
To post to this group, send email to [email protected]
<mailto:[email protected]>.
--
You received this message because you are subscribed to the Google Groups "Haskell
Pipes" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].