#!/bin/sh runghc \begin{code}
    A CGI script that maintains session state

{-# LANGUAGE GADTs, Rank2Types #-}
module WebSessionState where

import Control.Monad
import Control.Monad.Operational
import Control.Monad.Trans hiding (lift)

import Data.Char
import Data.Maybe

    -- external libraries needed
import Text.Html as H
import Network.CGI

    This example shows a "magic" implementation of a web session that
    looks like it needs to be executed in a running process,
    while in fact it's just a CGI script.
    The key part is a monad, called "Web" for lack of imagination,
    which supports a single operation
        ask :: String -> Web String
    which sends a simple minded HTML-Form to the web user
    and returns his answer.
    How does this work? The trick is that all previous answers
    are logged in a hidden field of the input form.
    The CGI script will simply replays this log when called.
    In other words, the user state is stored in the input form.

data WebI a where
    Ask :: String -> WebI String

type Web a = Program WebI a

ask = singleton . Ask

    -- interpreter
runWeb :: Web H.Html -> CGI CGIResult
runWeb m = do
            -- fetch log
        log' <- maybe [] (read . urlDecode) `liftM` getInput "log"
            -- maybe append form input
        f    <- maybe id (\answer -> (++ [answer])) `liftM` getInput "answer"
        let log = f log'
            -- run Web action and output result
        output . renderHtml =<< replay m log log
    replay = eval . view
    eval :: ProgramView WebI H.Html -> [String] -> [String] -> CGI H.Html
    eval (Return html)         log _      = return html
    eval (Ask question :>>= k) log (l:ls) = -- replay answer from log
        replay (k l) log ls
    eval (Ask question :>>= k) log []     = -- present HTML page to user
        return $ htmlQuestion log question

    -- HTML page with a single form
htmlQuestion log question = htmlEnvelope $ p << question +++ x
    x = form ! [method "post"] << (textfield "answer"
                +++ submit "Next" ""
                +++ hidden "log" (urlEncode $ show log))

htmlMessage s = htmlEnvelope $ p << s

htmlEnvelope html =
    header << thetitle << "Web Session State demo"
    +++ body << html

    -- example
example :: Web H.Html
example = do
    haskell <- ask "What's your favorite programming language?"
    if map toLower haskell /= "haskell"
        then message "Awww."
        else do
            ghc <- ask "What's your favorite compiler?"
            web <- ask "What's your favorite monad?"
            message $ "I like " ++ ghc ++ " too, but "
                      ++ web ++ " is debatable."
    message = return . htmlMessage

main = runCGI . runWeb $ example