1
votes

Let's take the simplest http server in haskell

{-# LANGUAGE OverloadedStrings #-}

import Network.Wai (responseLBS, Application)
import Network.Wai.Handler.Warp (run)
import Network.HTTP.Types (status200)
import Network.HTTP.Types.Header (hContentType)

main = do
    let port = 3000
    putStrLn $ "Listening on port " ++ show port
    run port app

app :: Application
app req f =
    f $ responseLBS status200 [(hContentType, "text/plain")] "Hello world!" 

How would I integrate a state monad inside the app function that will maintain the page hits? I'm having a difficult time thinking in terms of pure functional language. Maybe I'm missing some basics

1
from here it shows that Application appears to operate inside the IO monad. You could take a look into Monad Transformers and see how they can help you. Also, haskell is a functional language, yes, but what you are describing is a side effect. Prepare to enter 'Monad land' :-) - Robert
yes agreed monad is definitely needed :) let me take a look at monad transformer - Faiz Halde

1 Answers

1
votes

As I understand it, WAI will invoke the Application once per request, and the type of the Application is quite constrained: Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived. The concreteness of the Application type means that you can't "lift" run to take a function returning StateT s IO ResponseReceived, which would be the obvious way to do what you were thinking of. If you wanted to use a State monad inside one request handler, you would do it by writing your code to use something like StateT s IO ResponseReceived and then using app req f = runStateT initial $ .... However, this won't work to preserve state between invocations of the request handler, like you seem to want. Unfortunately, I don't believe that there's really a simple way to have your WAI handlers run in a custom monad and have WAI servers preserve the monadic state between invocations---which in some ways makes sense, since it seems reasonable to expect that a server may want to run the same handler multiple times in parallel for different requests.

Instead, if you want to track some state from inside your handlers, you are probably better off using some kind of mutable state in IO. For example, a simple request counter could be something like this:

{-# LANGUAGE OverloadedStrings #-}

import Network.Wai (responseLBS, Application)
import Network.Wai.Handler.Warp (run)
import Network.HTTP.Types (status200)
import Network.HTTP.Types.Header (hContentType)
import Control.Concurrent.MVar

main = do
    let port = 3000
    putStrLn $ "Listening on port " ++ show port
    hitCounter <- newMVar 0
    run port $ app hitCounter

app :: MVar Int -> Application
app hitCounter req f = do
    old <- takeMVar hitCounter
    let new = old + 1
    putMVar hitCounter $! new -- Thanks to Daniel Wagner in the comments for $!
    putStrLn $ "Hits: " ++ show new
    f $ responseLBS status200 [(hContentType, "text/plain")] "Hello world!" 

There are of course a number of other options for concurrent mutable state in Haskell, including IORefs, and the various STM types. Each of these has advantages/disadvantages/pitfalls that it is important to be aware of in the general context of concurrency/mutable state, and many of them should be usable in this context. Thanks to Daniel Wagner for pointing out in the comments that the original version of this example, which used putMVar hitCounter new ignored a common pitfall: if the value put into an MVar is not forced, a thunk can build up instead, which is often not what is intended. In the above code, new is forced by the putStrLn, but in case this is later removed, it is good practice to immediately force a value when putting it into an MVar---which the $! in the new version (above) does.