1

I'm writing a wrapper around a Warp server where users can specify routes and handlers to make a web server. I decided to try using Continuation Monads to allow handlers to exit using a continuation when a route matches. Here are the types and definitions I'm starting with:

import Control.Monad.Cont
import Control.Monad.Reader
import qualified Network.Wai as W
import qualified Data.Text as T

type App r a = ContT r (ReaderT W.Request IO) a
type Handler a = ReaderT W.Request IO a
type Respond = ((Status, T.Text) -> App (Status, T.Text) ())

route :: T.Text -> Handler (Status, T.Text) -> Respond -> App (Status, T.Text) ()
route routePath handler respond = do
  liftIO $ print $ "Checking" `T.append` routePath
  pth <- path
  when (routePath == pth) $ do
    req <- ask
    response <- liftIO $ runReaderT handler req
    respond response

An app is a collection of routes, each route reads the current continuation from the Reader environment; I originally wrote it like this:

hello :: Handler (Status, T.Text)
hello = return (ok200, "Hello World!")

goodbye :: Handler (Status, T.Text)
goodbye = return (ok200, "Goodbye World!")

app :: Respond -> App (Status, T.Text) ()
app = do
  route "/hello" hello
  route "/goodbye" goodbye

Strangely this doesn't seem to work, it only prints "Checking /goodbye"; however if we instead write the reader in the next form it works properly, as far as I was aware these two definitions should be equivalent; but apparently I'm missing something:

app :: Respond -> App (Status, T.Text) ()
app resp = do
  route "/hello" hello resp
  route "/goodbye" goodbye resp

Is there any way I can get the proper behaviour using the original app definition? Is there some way that the Reader Monad is messing up the continuations somehow?

I suspect that somehow the monad definition for reader is interrupting the order of computation; but it's not clear to me how:

instance Monad ((->) r) where
    f >>= k = \ r -> k (f r) r

Thanks!

Chris Penner
  • 1,881
  • 11
  • 15
  • 1
    In the first definition of `app`, you aren't operating in the `IO` monad, you are operating in the `((->) r)` monad (which you seem to recognize?). As such, it's impossible for that definition to do any sequencing of the underlying `IO` actions inside `App` or `Handler` (or e.g. `StateT () IO` or any other monad with `IO` at its 'base'). In other words, your definition is equivalent to `(\f k r -> k (f r) r) (route "a") (const $ route "b")` - can you do a few steps of evaluation to see why the output is the way it is? (hint: `const` ignores its second argument) – user2407038 Sep 02 '17 at 21:46

0 Answers0