Coproduct of free monads and web development

From time to time, I continue my experiments with Haskell and web development and try to build my own framework. I will never release anything because I am just exploring a few ideas.

Recently, I experimented with a common problem : when a client is getting an URL, it is communicating the formats it wants with some preference values. The server should return the best format according to what is requested by the client and what is supported by the server.

What is supported by a given controller on the server side is the problem : on one hand you have the code for the controller which can generate different formats but you can't extract this information which is implicit in the code. So, you need another piece of information which is describing explicitly the supported formats by a controller (as a list for instance). But then you repeat yourself : the information is present at two different places and encoded in quite different ways. It is then easy, during the lifetime of the product, to add some code to the controller and forget to update the metadata describing the supported formats or worse, to remove some code from the controller and forget to update the metadata.

Some frameworks are using a simpler approach : they do not take into account the preferences on the client side. They just serve the first format they can serve where first is defined by the control flow in the controller. If you test first for HTML and HTML is supported then you render an HTML page even if the client would have preferred a JPEG.

Types are coming to the rescue. It would be great if the type of a controller could be something like:

WebT (NormalHtml :+: Jpeg :+: IPhoneHtml) a

Then, the function used to dispatch the web request to the right controller could negotiate the format thanks to the type. So, it would not be possible to break the synchronization between the formats implemented in the code and the formats negotiated with the client.

WebT is a monad transformer corresponding to Web f a which is a Web monad parametrized by a signature f. The idea is that lots of monads are free monads just defined thanks to an algebraic datatype (the signature). And, a coproduct of free monads is a free monad so it should be possible to build complex free monads from simpler pieces just using a coproduct :+:. That's easy and very well described in the excellent article Data Types à La Carte.

Once you have the Web monad, you need to build a WebT monad transformer to be able to use it with a CGI Monad. It is more difficult and more structure is needed : the datatype used in the signatures has to be Traversable then it is possible to define the bind for the WebT monads.

Of course, this is not solving all problems. Let me give more details. The handler for a web request may have the type:

Format -> WebT f a

where Format is the MIME type for the chosen format. The selection is based upon the client request and the type f. Now, in the code of your handler you have to test for format. So, it is possible to introduce new errors here if you don't test for the right Format.

But, it is probably possible to change the evaluation of the Monad so that the pieces of code not related to the selected format are not executed.

Then, an handler like:

myHandler :: WebT (Html :+: Jpeg :+: Ajax) ()
myHandler = do

would execute only the render related to the selected format. I am not yet there. Currently, I have implemented the coproduct of free monads and the WebT monad transformer which is doing exactly what I want when f is a coproduct of monad each being isomorphic to the Error Monad. The idea is that renderHtml will behave a bit like a throw and stop the execution and generate the Html to be sent to the client.

In fact, my Web monad is more complex since it is also a MonadError instance and there is also some phantom types to control the accesses.

To give you an idea, the code used to call a controller for a given request is:

handleResource :: (Traversable r, Run r, WebActionMedia r, 
       MonadIO m, MonadCGI m, MonadError Exception m) 
               => RequestDesc 
               -> (Media -> WebT r Public m a) 
               -> m CGIResult
handleResource req rsrc = do 
    runWeb req $ do
        -- We only support utf8. Check if client can accept it
        charset <- checkCharset ["utf-8"]

        -- We only support no encoding. Check if client can accept it
        encoding <- checkEncoding [Encoding "identity"]

        let l = actionRsrc (rsrc undefined)
        case l of
            [] -> rsrc AnyMedia
            a -> do
              defaultMedia <- checkMedia a
              local (\s -> s{media = defaultMedia} ) (rsrc defaultMedia)

rsrc is my controller for the request. The type r is identifying the formats supported by the controller. actionRsrc (rsrc undefined) is generating a list of MIME types based upon the type r. The chosen format (argument of rsrc) is undefined because it is not yet known. But actionRsrc is not evaluating its argument. It just needs the type.

The list of formats supported by the controller - a in the case - is then used to find the preferred formats (checkMedia). Then, the controller for the request is called with the selected format.

Run r is used to evaluate the free monad. A free monad being related to an algebra, we have an evaluation function.

WebActionMedia is just a class used to convert between an Haskell type like IPhone :+: Html and a list of MIME types.

As you can see, I have lot of fun with Haskell :-) ... and my new iPhone :-)

So, when will someone port ghc to the iPhone ???? :-) and use TV to build the GUIs on the phone ???


blog comments powered by Disqus