Насколько я понимаю, WAI будет вызывать Application
один раз для каждого запроса, а тип Application
довольно ограничен: Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
. Конкретность типа Application
означает, что вы не можете «поднять» run
, чтобы взять функцию, возвращающую StateT s IO ResponseReceived
, что было бы очевидным способом сделать то, о чем вы думали. Если вы хотите использовать монаду State
внутри одного обработчика запросов, вы должны написать код для использования чего-то вроде StateT s IO ResponseReceived
, а затем использовать app req f = runStateT initial $ ...
. Однако это не сработает для сохранения состояния между вызовами обработчика запросов, как вы, кажется, хотите. К сожалению, я не верю, что на самом деле есть простой способ, чтобы ваши обработчики WAI работали в собственной монаде и чтобы серверы WAI сохраняли монадическое состояние между вызовами - что в некотором смысле имеет смысл, поскольку кажется разумным ожидать, что сервер может захотеть запустить один и тот же обработчик несколько раз параллельно для разных запросов.
Вместо этого, если вы хотите отслеживать какое-то состояние изнутри ваших обработчиков, вам, вероятно, лучше использовать какое-то изменяемое состояние в IO. Например, простой счетчик запросов может выглядеть примерно так:
{-# LANGUAGE OverloadedStrings #-}
import Network.Wai (responseLBS, Application)
import Network.Wai.Handler.Warp (run)
import Network.HTTP.Types (status200)
import Network.HTTP.Types.Header (hContentType)
import Control.Concurrent.MVar
main = do
let port = 3000
putStrLn $ "Listening on port " ++ show port
hitCounter <- newMVar 0
run port $ app hitCounter
app :: MVar Int -> Application
app hitCounter req f = do
old <- takeMVar hitCounter
let new = old + 1
putMVar hitCounter $! new -- Thanks to Daniel Wagner in the comments for $!
putStrLn $ "Hits: " ++ show new
f $ responseLBS status200 [(hContentType, "text/plain")] "Hello world!"
Есть, конечно, ряд других опций для одновременного изменяемого состояния в Haskell, включая IORef
s и различные типы STM
. У каждого из них есть свои преимущества / недостатки / подводные камни, о которых важно знать в общем контексте параллелизма / изменчивого состояния, и многие из них должны использоваться в этом контексте. Спасибо Дэниелу Вагнеру за то, что он указал в комментариях, что оригинальная версия этого примера, в которой использовался putMVar hitCounter new
, игнорировала распространенную ловушку: если значение, введенное в MVar
, не является принудительным, вместо этого может образовываться thunk, который часто не то, что предназначено. В приведенном выше коде putStrLn
принудительно вызывается *1020*, но в случае, если это впоследствии будет удалено, хорошей практикой является немедленное форсирование значения при помещении его в MVar
--- который $!
в новая версия (выше) делает.