Search code examples
haskellyesod

Post and Get a variable using yesod server


var express = require('express')
var app = express()
var store = undefined

app.post("/", function(req, res){
  store = req.body
})

app.get("/", function(req, res){
  res.send(store)
} 
app.listen(some_port_num)

This is a simple nodejs/express server application which stores the http request body in a global variable and sends the same variable as responsse on a get request.

How to write this code in yesod/haskell. I don't know how to use global/module-scoped variables in haskell/yesod. And, is there any other way to share variables between two functions in haskell?


Solution

  • Using global constants is trivial in Haskell: you just write foo = bla somewhere (preferrably with a signature foo :: FooType). foo can then be used anywhere in your module (or even outside, if you export it).

    Global variables OTOH aren't really possible in Haskell, as such. For the most part, that's a good thing because global state tends to lead to lots of bugs. Often when programmers think about global variables, it turns out global constants would be just fine, or otherwise the mutable state should better be re-written as explicit parameters.

    Sometimes however you pretty much do need state variables. In Yesod in particular, there's a place where you can store them sort-of globally: the Yesod. That is the data structure that your web application has as its “basement”. For instance, you may have

    data YourYesod = YourYesod {
          ...
          , reqStore :: IORef RequestBody   -- IORef basically means, it's variable
                                            -- in the imperative sense.
          ...
          }
    
    mkYesod "YourYesod" [parseRoutes| ... |]
    

    The Yesod can be accessed from anywhere in the Handler etc. monads, e.g. in

    getHomeR :: Handler Html
    getHomeR = do
        ...
        yourYesod <- getYesod           -- gain "access" to the Yesod
        storeS <- liftIO $ readIORef (reqStore yourYesod)  -- look up the variable state
        ...
        liftIO $ writeIORef (reqStore yourYesod) newStoreS -- write new state
        ...
    

    At the beginning of the program, the reqStore will need to be initialised, by something like

    main :: IO ()
    main = do
       ...
       initReqStore = newIORef emptyRequest
       ...
       warp 3000 $ YourYesod ... initReqStore ...
    

    Before doing it this way, think about whether the store variable really needs to be that global. The Yesod is pretty much the most global scope you have for stuff like that; that means also danger of those typical bugs like in procedural languages. If the variable is only used within one handler, you might as well just introduce it locally, like

                   do
                     ...
                     store <- newIORef emptyRequest
                     appPost "/" $ \req res -> do
                           liftIO $ writeIORef store $ body req
                     appGet "/" $ \req res -> do
                           storedReq <- liftIO $ readIORef store
                           send res storedReq