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.