Как использовать постоянную монаду State со Spock?

Я только начинаю работать с haskell, и у меня возникают проблемы с базовым «эхо» REST-сервером.

Spock выглядел как хорошая отправная точка для REST-сервера, и хотя я получил основы монады State, но у меня возникли проблемы с пониманием того, как поставить runState вокруг кода spock.

Вот код, который у меня есть до сих пор.

{-# 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"

person ReaperUnreal    schedule 11.08.2015    source источник
comment
Ключом к этой головоломке является первый аргумент spockT, который вам нужно будет указать для m ~ State String. Однако вы столкнетесь с точно такой же проблемой, как описано в этом ответе: State String не будет автоматически сохраняться между вызовами обработчика. .   -  person Cactus    schedule 12.08.2015


Ответы (1)


Итак, вот версия взлома restartableStateT для вашего примера:

{-# 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."

Как и в другом ответе, этот создает один глобальный IORef для хранения «состояния». Затем runner, переданный spockT, может выполнить любое вычисление StateT String IO, получив состояние от этого IORef, запустив вычисление и поместив полученное состояние обратно в IORef.

Я хотел бы повторить из другого ответа, что это не обязательно хорошая идея, потому что у нее нет истории для параллелизма. Я предполагаю, что это можно было бы скрыть, например, с помощью STM, но... Я думаю, что вы должны просто использовать базу данных для такого рода вещей.

person Cactus    schedule 12.08.2015
comment
Обычно я бы просто использовал БД, но это было просто попробовать. Спасибо за информацию, теперь есть чему поучиться. - person ReaperUnreal; 12.08.2015