Реализация хитов страниц на веб-сервере haskell - PullRequest
0 голосов
/ 06 июля 2018

Давайте возьмем самый простой http-сервер в haskell

{-# LANGUAGE OverloadedStrings #-}

import Network.Wai (responseLBS, Application)
import Network.Wai.Handler.Warp (run)
import Network.HTTP.Types (status200)
import Network.HTTP.Types.Header (hContentType)

main = do
    let port = 3000
    putStrLn $ "Listening on port " ++ show port
    run port app

app :: Application
app req f =
    f $ responseLBS status200 [(hContentType, "text/plain")] "Hello world!" 

Как бы я интегрировал монаду состояний в функцию приложения, которая будет поддерживать попадания на страницы? Мне трудно думать с точки зрения чистого функционального языка. Может быть, мне не хватает некоторых основ

1 Ответ

0 голосов
/ 06 июля 2018

Насколько я понимаю, 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 --- который $! в новая версия (выше) делает.

...