[Haskell-cafe] First go at reactive programming
Conal Elliott
conal at conal.net
Wed Jan 16 02:19:04 EST 2008
Hi Levi,
Delightful! I'd been hoping for a networking-related use of Reactive. I
made a few tweaks to clean up the code:
* Factored the fmap out of handleConnection, handleToRequest,
runRequestHandler r, and responseSend, to simplify their
interfaces/semantics (no more events).
* Used (second.fmap) in runRequestHandler in place of explicit
manipulation. Then factored it out into handleConnection, to simplify
interface/semantics (no more pair/IO).
* Added a few type signatures.
* Replaced (putStrLn . show) with print in responseSend.
Let's play some more with improving on the handle-passing. Meanwhile, new
version below. I bet we can make it more functional/elegant, isolating the
IO from a simple & pure core. For instance, the pattern of accepting
connections and then dialoging on each one smells very like what I have in
mind for the (functional) Event monad.
Cheers, - Conal
module Main where
import Control.Applicative
import Control.Arrow (second,(&&&),(>>>))
import Control.Concurrent
import Control.Monad
import Data.Reactive
import Network.BSD
import Network.HTTP
import Network
import System.IO
import Text.XHtml.Strict
type RequestHandler = Request -> Response
main = runHttpServer helloWorldHandler
helloWorldHandler :: RequestHandler
helloWorldHandler _ = Response (2,0,0) "" [] $ prettyHtml helloWorldDoc
helloWorldDoc = header << thetitle << "Hello World"
+++ body << h1 << "Hello World"
runHttpServer :: RequestHandler -> IO a
runHttpServer r = socketServer >>= runE . fmap (handleConnection r)
socketServer :: IO (Event Handle)
socketServer = withSocketsDo $ do
(e,snk) <- mkEventShow "Server"
sock <- listenOn (PortNumber 8080)
forkIO $ forever $ acceptConnection sock $ snk
return e
handleConnection :: RequestHandler -> Handle -> IO ()
handleConnection r =
handleToRequest >>> (second.fmap) (runRequestHandler r) >>> responseSend
handleToRequest :: Handle -> (Handle, IO (Result Request))
handleToRequest = id &&& receiveHTTP
runRequestHandler :: RequestHandler -> Result Request -> Result Response
runRequestHandler r rq = rq `bindE` (Right . r)
responseSend :: (Handle, IO (Result Response)) -> IO ()
responseSend (h,rsp) =
rsp >>= either print (respondHTTP h) >> close h
acceptConnection :: Socket -> (Handle -> IO ()) -> IO ThreadId
acceptConnection s k = accept s >>= \(h,_,_) -> forkIO $ k h
instance Stream Handle where
readLine h = hGetLine h >>= \l -> return $ Right $ l ++ "\n"
readBlock h n = replicateM n (hGetChar h) >>= return . Right
writeBlock h s = mapM_ (hPutChar h) s >>= return . Right
close = hClose
On Jan 15, 2008 3:29 AM, Levi Stephen <levi.stephen at optusnet.com.au> wrote:
> Hi,
>
> Listed below is my first experiment with reactive programming. It is a
> simple web server written using the Data.Reactive[1] library. The
> intended interface is given by the runHttpServer function, so the
> remainder is intended to be internal.
>
> I'd be happy to hear comments on any parts of this, but am particularly
> interested in the following:
>
> 1. Is this kind of code what is intended from reactive programming?
> 2a. I'm not sure about passing the (Handle,...) tuple around. Is there a
> way to avoid this?
> 2b. I'm not sure of the best place to handle possible socket exceptions
> 2c. I'd like to be able to pass a function of type Event Request ->
> Event Response to runHttpServer, so that reactive programming could be
> used throughout client code also, but the (Handle,...) tuples seem to be
> getting in the way.
> 3. I have a feeling there's a clearer way to write responseSend.
>
> Thanks,
> Levi
>
> [1] http://www.haskell.org/haskellwiki/Reactive
>
> module Main where
>
> import Control.Applicative
> import Control.Arrow ((&&&),(>>>))
> import Control.Concurrent
> import Control.Monad
>
> import Data.Reactive
>
> import Network.BSD
> import Network.HTTP
> import Network
>
> import System.IO
>
> import Text.XHtml.Strict
>
> type RequestHandler = Request -> Response
>
> main = runHttpServer helloWorldHandler
>
> helloWorldHandler :: RequestHandler
> helloWorldHandler _ = Response (2,0,0) "" [] $ prettyHtml helloWorldDoc
>
> helloWorldDoc = header << thetitle << "Hello World"
> +++ body << h1 << "Hello World"
>
> runHttpServer r = socketServer >>= runE . handleConnection r
>
> socketServer :: IO (Event Handle)
> socketServer = withSocketsDo $ do
> (e,snk) <- mkEventShow "Server"
> sock <- listenOn (PortNumber 8080)
> forkIO $ forever $ acceptConnection sock $ snk
> return e
>
> handleConnection :: RequestHandler -> Event Handle -> Event (IO ())
> handleConnection r = handleToRequest >>> runRequestHandler r >>>
> responseSend
>
>
> handleToRequest :: Event Handle -> Event (Handle, IO (Result Request))
> handleToRequest e = fmap (id &&& receiveHTTP) e
>
> responseSend :: Event (Handle, IO (Result Response)) -> Event (IO ())
> responseSend e = fmap (\(h,rsp) -> rsp >>= either (putStrLn . show)
> (respondHTTP h) >> close h) e
>
> runRequestHandler :: RequestHandler -> Event (Handle, IO (Result
> Request)) -> Event (Handle, IO (Result Response))
> runRequestHandler r e = fmap hrToHr e
> where
> rqhdl :: Result Request -> Result Response
> rqhdl rq = bindE rq (Right . r)
> hrToHr :: (Handle, IO (Result Request)) -> (Handle, IO (Result
> Response))
> hrToHr (h,req) = (h, liftA rqhdl req)
>
> acceptConnection s k = accept s >>= \(h,_,_) -> forkIO $ k h
>
> instance Stream Handle where
> readLine h = hGetLine h >>= \l -> return $ Right $ l ++ "\n"
> readBlock h n = replicateM n (hGetChar h) >>= return . Right
> writeBlock h s = mapM_ (hPutChar h) s >>= return . Right
> close = hClose
>
>
>
>
>
>
> _______________________________________________
> Haskell-Cafe mailing list
> Haskell-Cafe at haskell.org
> http://www.haskell.org/mailman/listinfo/haskell-cafe
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://www.haskell.org/pipermail/haskell-cafe/attachments/20080115/ab10d9cc/attachment-0001.htm
More information about the Haskell-Cafe
mailing list