Search code examples
resthaskellmonadsstate-monadhaskell-spock

How do I use a persistent State monad with Spock?


I'm just starting out with haskell and I'm having issues with a basic "echo" REST server.

Spock looked like a nice starting place for a REST server, and I though I got the basics of the State monad, but I'm having issues understanding how to put a runState around the spock code.

Here's the code I've got so far.

{-# LANGUAGE OverloadedStrings #-}
module Main where

import Data.Monoid
import Web.Spock.Safe
import qualified Control.Monad.State as S

storeData :: String -> S.State String String
storeData val = do S.put val
                   return val

getData :: S.State String String
getData = do val <- S.get
             return val

main :: IO ()
main =
    runSpock 11350 $ spockT id $
    do get "store" $
           text "Would be a call to getData"

Solution

  • OK so here's a version of the restartableStateT hack for your example:

    {-# LANGUAGE OverloadedStrings #-}
    {-# LANGUAGE Rank2Types #-}
    module Main where
    
    import Data.Monoid
    import Data.String (fromString)
    import Web.Spock.Safe
    import qualified Control.Monad.State as S
    import Data.IORef
    
    storeData :: (Monad m) => String -> S.StateT String m String
    storeData val = do S.put val
                       return val
    
    getData :: (Monad m) => S.StateT String m String
    getData = do val <- S.get
                 return val
    
    newtype RunStateT s m = RunStateT{ runStateT :: forall a. S.StateT s m a -> m a }
    
    restartableStateT :: s -> IO (RunStateT s IO)
    restartableStateT s0 = do
        r <- newIORef s0
        return $ RunStateT $ \act -> do
            s <- readIORef r
            (x, s') <- S.runStateT act s
            atomicModifyIORef' r $ const (s', x)
    
    main :: IO ()
    main = do
        runner <- restartableStateT "initial state"
        runSpock 11350 $ spockT (runStateT runner) $ do
            get "store" $ do
                cmd <- param "value"
                case cmd of
                    Nothing -> do
                        old <- S.lift getData
                        text $ fromString old
                    Just new -> do
                        S.lift $ storeData new
                        text "Stored."
    

    Like the other answer, this one creates a single global IORef to store "the state". The runner passed to spockT is then able to run any StateT String IO computation by getting the state from this IORef, running the computation, and putting the resulting state back into the IORef.

    I would like to reiterate from the other answer that this is not necessarily a good idea, because it has no story for concurrency. I guess that could be papered over by using STM for example, but... I think you should just use a database for this kind of thing.