haskell - leibniz - Scotty: grupo de conexión como lector de mónada
leibniz educatina (2)
Como ha aludido, la forma de hacerlo accesible es envolver sus cálculos en la mónada Reader
o, probablemente, en el transformador ReaderT
. Así que su función de run
(cambiado ligeramente)
run :: Pool Pipe -> Action IO a -> IO (Either Failure a)
run pool act =
flip withResource (/x -> access x master db act) =<< pool
se convierte en
run :: Action IO a -> ReaderT (Pool Pipe) IO (Either Failure a)
run act = do
pool <- ask
withResource pool (/x -> access x master db act)
¡Los cálculos dentro de un ReaderT rma
pueden acceder al r
usando ask
y ReaderT
aparentemente lo evoca de la nada! En realidad, la mónada ReaderT
simplemente está ReaderT
el Env
durante todo el cómputo sin que usted tenga que preocuparse por ello.
Para ejecutar una acción ReaderT
, utilice runReaderT :: ReaderT rma -> r -> ma
. Así que llama a runReaderT
en su función de nivel superior de runReaderT
para proporcionar el Pool
y runReaderT
desenvolverá el entorno de ReaderT
y le devolverá un valor en la mónada base.
Por ejemplo, para evaluar su función de run
.
-- remember: run act :: ReaderT (Pool Pipe) IO (Either Failure a)
runReaderT (run act) pool
pero no querría usar runReaderT
en run
, ya que probablemente es parte de un cálculo más amplio que también debería compartir el entorno ReaderT
. Trate de evitar el uso de runReaderT
en los runReaderT
de "hoja", por lo general, debería llamarlo lo más alto posible en la lógica del programa.
EDITAR : La diferencia entre Reader
y ReaderT
es que Reader
es una mónada, mientras que ReaderT
es un transformador de mónada. Es decir, ReaderT
agrega el comportamiento del Reader
a otra mónada (o pila de transformadores de mónada). Si no está familiarizado con los transformadores de mónada, recomendaría los transformadores haskell del mundo real .
Tiene showJson pool ~ ActionM ()
y desea agregar un entorno de Reader
con acceso a un Pool Pipe
. En este caso, realmente necesita transformadores ActionT
y ScottyT
lugar de ReaderT
para trabajar con las funciones del paquete scotty
.
Tenga en cuenta que ActionM
es un type ActionM = ActionT Text IO
definido type ActionM = ActionT Text IO
, de manera similar para ScottyM
.
No tengo todas las bibliotecas necesarias instaladas, por lo que es posible que esto no sea una comprobación de tipo, pero debería darle la idea correcta.
basal :: ScottyT Text (ReaderT (Pool Pipe) IO) ()
basal = do
middleware $ staticPolicy (...)
get "/json" showJson
showJson :: ActionT Text (ReaderT (Pool Pipe) IO) ()
showJson = do
pool <- lift ask
let run act = withResource pool (/p -> access p master "index act)
d <- liftIO $ run $ fetch $ select [] "tables"
text . TL.pack $ either (const "") show d
Hay billones de tutoriales de mónadas que incluyen al lector y parece todo claro cuando lees sobre esto. Pero cuando realmente necesitas escribir, se convierte en un asunto diferente.
Nunca he usado el Reader, nunca lo puse en práctica. Así que no sé cómo hacerlo, aunque lo leí.
Necesito implementar un grupo de conexión de base de datos simple en Scotty para que cada acción pueda usar el grupo. El grupo debe ser "global" y accesible por todas las funciones de acción. Leí que la forma de hacerlo es la mónada del lector. Si hay alguna otra forma por favor hágamelo saber.
¿Puede por favor ayudarme y mostrar cómo hacer esto con el Reader correctamente? Probablemente aprenderé más rápido si veo cómo se hace con mis propios ejemplos.
{-# LANGUAGE OverloadedStrings #-}
module DB where
import Data.Pool
import Database.MongoDB
-- Get data from config
ip = "127.0.0.1"
db = "index"
--Create the connection pool
pool :: IO (Pool Pipe)
pool = createPool (runIOE $ connect $ host ip) close 1 300 5
-- Run a database action with connection pool
run :: Action IO a -> IO (Either Failure a)
run act = flip withResource (/x -> access x master db act) =<< pool
Así que lo anterior es simple. y quiero usar la función ''ejecutar'' en cada acción de Scotty para acceder al conjunto de conexiones de la base de datos. Ahora, la pregunta es cómo envolverla en la mónada Reader para que sea accesible a todas las funciones. Entiendo que la variable ''pool'' debe ser ''como global'' para todas las funciones de acción de Scotty.
Gracias.
ACTUALIZAR
Estoy actualizando la pregunta con el fragmento de código completo. Donde paso la variable ''pool'' por la cadena de funciones. Si alguien puede mostrar cómo cambiarlo para utilizar el lector de mónadas, por favor. No entiendo cómo hacerlo.
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Network.HTTP.Types
import Web.Scotty
import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import Data.Text.Lazy.Internal
import Data.Monoid (mconcat)
import Data.Aeson (object, (.=), encode)
import Network.Wai.Middleware.Static
import Data.Pool
import Database.MongoDB
import Control.Monad.Trans (liftIO,lift)
main = do
-- Create connection pool to be accessible by all action functions
pool <- createPool (runIOE $ connect $ host "127.0.0.1") close 1 300 5
scotty 3000 (basal pool)
basal :: Pool Pipe -> ScottyM ()
basal pool = do
middleware $ staticPolicy (noDots >-> addBase "static")
get "/json" (showJson pool)
showJson :: Pool Pipe -> ActionM ()
showJson pool = do
let run act = withResource pool (/pipe -> access pipe master "index" act)
d <- lift $ run $ fetch (select [] "tables")
let r = either (const []) id d
text $ LT.pack $ show r
Gracias.
ACTUALIZACIÓN 2
Traté de hacerlo de la manera que se sugirió a continuación, pero no funciona. Si alguien tiene alguna idea, por favor. La lista de errores de compilación es tan larga que ni siquiera sé por dónde empezar ...
main = do
pool <- createPool (runIOE $ connect $ host "127.0.0.1") close 1 300 5
scotty 3000 $ runReaderT basal pool
basal :: ScottyT LT.Text (ReaderT (Pool Pipe) IO) ()
basal = do
middleware $ staticPolicy (noDots >-> addBase "static")
get "/json" $ showJson
showJson :: ActionT LT.Text (ReaderT (Pool Pipe) IO) ()
showJson = do
p <- lift ask
let rdb a = withResource p (/pipe -> access pipe master "index" a)
j <- liftIO $ rdb $ fetch (select [] "tables")
text $ LT.pack $ show j
ACTUALIZACIÓN 3
Gracias a cdk por dar la idea y gracias a Ivan Meredith por dar la sugerencia de ScottyT. Esta pregunta también ayudó: ¿Cómo agrego la mónada Reader a la mónada de Scotty? Esta es la versión que compila. Espero que ayude a alguien y le ahorre tiempo.
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.Encoding as T
import Data.Text.Lazy (Text)
import Control.Monad.Reader
import Web.Scotty.Trans
import Data.Pool
import Database.MongoDB
type ScottyD = ScottyT Text (ReaderT (Pool Pipe) IO)
type ActionD = ActionT Text (ReaderT (Pool Pipe) IO)
-- Get data from config
ip = "127.0.0.1"
db = "basal"
main = do
pool <- createPool (runIOE $ connect $ host ip) close 1 300 5
let read = /r -> runReaderT r pool
scottyT 3000 read read basal
-- Application, meaddleware and routes
basal :: ScottyD ()
basal = do
get "/" shoot
-- Route action handlers
shoot :: ActionD ()
shoot = do
r <- rundb $ fetch $ select [] "computers"
html $ T.pack $ show r
-- Database access shortcut
rundb :: Action IO a -> ActionD (Either Failure a)
rundb a = do
pool <- lift ask
liftIO $ withResource pool (/pipe -> access pipe master db a)
He estado tratando de resolver este problema exacto yo mismo. Gracias a las sugerencias sobre esta pregunta de SO, y otras investigaciones, he encontrado las siguientes que me funcionan. El bit clave que faltaba era usar scottyT
No hay duda de que hay una forma más bonita de escribir runDB, pero no tengo mucha experiencia en Haskell, así que por favor publíquela si puede hacerlo mejor.
type MCScottyM = ScottyT TL.Text (ReaderT (Pool Pipe) IO)
type MCActionM = ActionT TL.Text (ReaderT (Pool Pipe) IO)
main :: IO ()
main = do
pool <- createPool (runIOE $ connect $ host "127.0.0.1") close 1 300 5
scottyT 3000 (f pool) (f pool) $ app
where
f = /p -> /r -> runReaderT r p
app :: MCScottyM ()
app = do
middleware $ staticPolicy (noDots >-> addBase "public")
get "/" $ do
p <- runDB dataSources
html $ TL.pack $ show p
runDB :: Action IO a -> MCActionM (Either Failure a)
runDB a = (lift ask) >>= (/p -> liftIO $ withResource p (/pipe -> access pipe master "botland" a))
dataSources :: Action IO [Document]
dataSources = rest =<< find (select [] "datasources")
Actualizar
Supongo que esto es un poco más bonito.
runDB :: Action IO a -> MCActionM (Either Failure a)
runDB a = do
p <- lift ask
liftIO $ withResource p db
where
db pipe = access pipe master "botland" a