-
Notifications
You must be signed in to change notification settings - Fork 374
Keeping (in memory) state with yesod
[WARNING] Yesod Cookbook has moved to a new place. Please contribute there.
So you want to keep state in yesod?
Please read the disclamer on Keeping (in memory) state with warp carefully. It completely applies to keeping state with yesod, too.
Same as described in Keeping (in memory) state with warp. While you're there, make sure to read the disclamer.
It's actually very simply: Just put it into the yesod foundation type. Your Handler can then access it using getYesod or getYesodSub.
As we did with Keeping (in memory) state with warp, let's count the number of requests using an IORef. For that, we only need to slightly modify the Hello World Example. "-- (n)" indicates that there's something that I want to comment on below.
{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses,
TemplateHaskell, OverloadedStrings #-}
import Data.IORef (IORef, newIORef, atomicModifyIORef)
import Yesod
data HelloWorld = HelloWorld {
counter :: IORef Integer -- (1)
}
mkYesod "HelloWorld" [parseRoutes|
/ CounterR GET -- (4)
|]
instance Yesod HelloWorld
incCount :: (Num a, Show a) => IORef a -> IO a
incCount counter = atomicModifyIORef counter (\c -> (c+1, c)) -- (6)
getCounterR :: Handler RepHtml
getCounterR = do
yesod <- getYesod -- (5)
count <- liftIO $ incCount $ counter yesod -- (7)
liftIO $ putStrLn $ "Sending Response " ++ show count -- (8)
defaultLayout [whamlet|Hello World #{count}|] -- (9)
main :: IO ()
main = do
counter <- newIORef 0 -- (2)
warpDebug 3000 $ HelloWorld { counter = counter } -- (3)
So let's go through this step by step:
-
HelloWorld
is our foundation type. We use record syntax to name the fieldcounter
appropriately. This makes it easier to extend the foundation type further (e.g. adding a second counter that counts something else). -
In the main function, the very first thing we do is to create a new
IORef
and store it incounter
. We need to use<-
becausenewIoRef
returns anIO IORef a
, but we want to access theIORef a
inside an IO monad. This makes sense:newIORef 0
returns different IORefs on every call, that is expressed by that return type. -
We can then use that IORef to create a new instance of our
HelloWorld
type and give that towarpDebug
, which runs the yesod application inside the haskell warp webserver. -
When a browser requests
/
, it will be handled by ourgetCounterR
function. -
The first thing
getCounterR
does is get theHelloWorld
instance we created inmain
. AsgetYesod
returns a Handler monad, we use<-
to directly access theHelloWorld
inside that monad. -
We then call
incCount
on thecounter
(which is an IORef Integer) stored inHelloWorld
. This function increments the counter by one and returns the old value. For that, it usesatomicModifyIORef
, which does this modification in a thread-safe manner. The function returns a tuple: The first element of the tuple is the new state, the second element of the tuple is the state returned. Here, it is the count before incrementing. Thus, for the first request this returns 0, for the second 1, for the third it returns 2, etc. -
The result of incCount is lifted into the
Handler
monad, so that we can call<-
on it and treat it as an Integer inside the monad. -
We print "Sending Response $count" to the standard output.
-
And use the count variable inside whamlet. That's it!
You should be able to compile & run this program using (assuming it's called yesod-counter.hs):
ghc yesod-counter.hs && ./yesod-counter
Which should output a bunch of Loading package
and then finally:
Linking yesod-counter ...
Application launched, listening on port 3000
Now when you point a browser to it, the browser will display "Hello World 0" on the first page load, "Hello World 1" on the second page load and so forth.
Meanwhile, your console should show:
Sending Response 0
127.0.0.1 - - [25/Nov/2012:14:21:39 +0100] "GET / HTTP/1.1" 200 - "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11"
127.0.0.1 - - [25/Nov/2012:14:21:39 +0100] "GET /favicon.ico HTTP/1.1" 404 - "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11"
Sending Response 1
127.0.0.1 - - [25/Nov/2012:14:21:40 +0100] "GET / HTTP/1.1" 200 - "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11"
127.0.0.1 - - [25/Nov/2012:14:21:40 +0100] "GET /favicon.ico HTTP/1.1" 404 - "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11"
The lines "Sending Response 0" and "Sending Response 1" are written by us, while the lines starting with an IP address are the debug output of warpDebug
.