Sean Clark Hess
Sean Clark Hess

Reputation: 16079

How to use ReaderT to transform Happstack's ServerPart Response?

This is the first time I'm playing with Monad Transformers. This is a simple happstack app.

{-# LANGUAGE OverloadedStrings #-}

import Happstack.Lite 
import qualified Data.ByteString.Lazy.Char8 as L

main :: IO ()
main = do
    serve Nothing hello 

hello :: ServerPart Response
hello = do
    ok $ toResponse ("Hello" :: L.ByteString)

I would like to be able to change hello so it can read some global config data using ReaderT. Let's just say the config is a string to keep it simple

type NewMonad = ReaderT L.ByteString (ServerPartT IO) 
runNewMonad :: NewMonad a -> L.ByteString -> ServerPart a
runNewMonad k c = runReaderT k c

How do I change hello so it can use ask? I'm not sure what the type would be. NewMonad Response isn't quite right, because ok returns a ServerPart Response.

How do I change main so that serve works? It expects a ServerPart Response.

Upvotes: 2

Views: 308

Answers (1)

ehird
ehird

Reputation: 40797

In fact, NewMonad Response is the correct type for hello; you just need to use lift to transform an action in the underlying monad to one in the transformer. For example:

hello :: NewMonad Response
hello = do
    foo <- ask
    lift . ok $ toResponse foo

In general,

lift :: (MonadTrans t, Monad m) => m a -> t m a

i.e., if you have a monadic action, then you turn it into an action in any monad transformer over that monad. This is the definition of a monad transformer: it can transform over any monad, and embed actions of that monad.

It seems that restricting all the monadic actions to one specific monad — rather than using typeclasses to work in any appropriate monad — is one of the simplifications happstack-lite uses compared to the full Happstack, which has this type for ok:

ok :: (FilterMonad Response m) => a -> m a

With this type, assuming appropriate instances are declared for the standard transformers, you could just use ok directly in MyMonad.

As for main, you need to eliminate the ReaderT layer, leading a a ServerPart Response that you can pass to serve:

main :: IO ()
main = do
    serve Nothing $ runNewMonad hello ("Hello" :: L.ByteString)

(This would cause problems if you were using a monad carrying state that you wanted to change over the course of many requests, since serve's type is too restrictive to support such state threading (without manually encoding it with IORefs or similar); possibly the unrestricted Happstack has the ability to do this, but it'd likely be very brittle anyway, as you shouldn't really be relying on the order requests are processed in like that.)

Upvotes: 5

Related Questions