Как создать базу данных монад в Happstack? - PullRequest
5 голосов
/ 18 октября 2011

Я хочу создать приложение Happstack с большим доступом к базе данных. Я думаю, что стек монад с IO внизу и монадой, похожей на запись базы данных сверху (с модулем записи журналов в середине) будет работать, чтобы иметь четкие функции в каждом доступе, например:

itemsRequest :: ServerConfig -> ServerPart Response
itemsRequest cf = dir "items" $ do
  methodM [GET,HEAD]
  liftIO $ noticeM (scLogger cf) "sended job list"

  items <- runDBMonad (scDBConnString cf) $ getItemLists

  case items of
    (Right xs) -> ok $ toResponse $ show xs
    (Left err) -> internalServerError $ toResponse $ show err

С:

getItemList :: MyDBMonad (Error [Item])
getItemList = do
  -- etc...

Но у меня мало знаний о монаде и монадных трансформаторах (я рассматриваю этот вопрос как упражнение, чтобы узнать о нем), и я понятия не имею, как начать создание базы данных монады, как поднять IO от стека счастья до Стек базы данных и т. Д.

Ответы [ 2 ]

6 голосов
/ 21 июня 2012

Вот минимальный рабочий код, скомпилированный из приведенных выше фрагментов для запутанных новичков, таких как я.

Вы помещаете материал в тип AppConfig и захватываете его с помощью ask внутри создателей ответов.

{-# LANGUAGE OverloadedStrings #-}
module Main where

import Happstack.Server
import Control.Monad.Reader
import qualified Data.ByteString.Char8 as C

myApp :: AppMonad Response
myApp = do
    -- access app config. look mom, no lift!
    test <- ask

    -- try some happstack funs. no lift either.
    rq <- askRq
    bs <- lookBS "lol"

    -- test IO please ignore
    liftIO . print $ test
    liftIO . print $ rq
    liftIO . print $ bs

    -- bye
    ok $ toResponse ("Oh, hi!" :: C.ByteString)

-- Put your stuff here.
data AppConfig = AppConfig { appSpam :: C.ByteString
                           , appEggs :: [C.ByteString] } deriving (Eq, Show)
config = AppConfig "THIS. IS. SPAAAAAM!!1" []

type AppMonad = ReaderT AppConfig (ServerPartT IO)

main = simpleHTTP (nullConf {port=8001}) $ runReaderT myApp config {appEggs=["red", "gold", "green"]}
6 голосов
/ 19 октября 2011

Вы, вероятно, захотите использовать 'ReaderT':

type MyMonad a = ReaderT DbHandle ServerPart a

Монадный преобразователь Reader делает доступным одно значение с помощью функции ask - в этом случае значение, которое мы хотим, чтобы каждый получилat - это соединение с базой данных.

Здесь DbHandle - это какое-то соединение с вашей базой данных.

Поскольку 'ReaderT' уже является экземпляром всех типов классов happstack-server, все нормальноВ этой монаде будут работать функции happstack-server.

Возможно, вам также понадобится какой-нибудь помощник для открытия и закрытия соединения с базой данных:

runMyMonad :: String -> MyMonad a -> ServerPart a
runMyMonad connectionString m = do
   db <- liftIO $ connect_to_your_db connectionString
   result <- runReaderT m db
   liftIO $ close_your_db_connection db

(Возможно, лучше использовать функциюкак «скобка» здесь, но я не знаю, есть ли такая операция для монады ServerPart)

Я не знаю, как вы хотите вести журналы - как вы планируете взаимодействовать с вашим журналом-файл?Что-то вроде:

type MyMonad a = ReaderT (DbHandle, LogHandle) ServerPart a

, а затем:

askDb :: MyMonad DbHandle
askDb = fst <$> ask

askLogger :: MyMonad LogHandle
askLogger = snd <$> ask

может быть достаточно.Затем вы можете использовать эти примитивы для создания функций более высокого уровня.Вам также необходимо изменить runMyMonad для передачи в LogHandle, что бы это ни было.

Как только вы получите более двух вещей, к которым вы хотите получить доступ, вы получите правильный тип записи вместокортеж.

...