This guide will show you how to add Orville to a monad that is already using
ReaderT in its monad stack. It builds conceptually on top of the previous
guide, which assumed there
was not already a ReaderT in the application monad stack. It’s recommended
that you read that guide before this one.
The file listing below shows a simple application with its own Application
monad that already has a reader context. When there is already a reader context
in the application stack it’s generally perferrable to incorporate Orville into
the existing reader context rather than adding a new ReaderT layer atop the
one that’s already there.
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Main
( main
) where
import qualified Control.Monad.IO.Class as MIO
import qualified Control.Monad.Reader as Reader
data ApplicationContext =
ApplicationContext
{ applicationGreeting :: String
}
newtype Application a =
Application (Reader.ReaderT ApplicationContext IO a)
deriving
( Functor
, Applicative
, Monad
, MIO.MonadIO
)
getGreeting :: Application String
getGreeting =
Application (Reader.asks applicationGreeting)
runApplication :: String -> Application a -> IO a
runApplication greeting (Application io) =
let
context =
ApplicationContext
{ applicationGreeting = greeting
}
in
Reader.runReaderT io context
myApplication :: Application ()
myApplication = do
greeting <- getGreeting
MIO.liftIO . putStrLn $ greeting
main :: IO ()
main =
runApplication "Hello Application" myApplicationAs in the last guide, we
will first add an OrvilleState to our application monad. In this case we’ll
add it as a new field to the existing ApplicationContext.
*** Main.hs (Old)
--- Main.hs (New)
***************
*** 7,8 ****
--- 7,9 ----
import qualified Control.Monad.Reader as Reader
+ import qualified Orville.PostgreSQL as O
***************
*** 11,12 ****
--- 12,14 ----
{ applicationGreeting :: String
+ , applicationOrvilleState :: O.OrvilleState
}This requires that the new applicationOrvilleState field be populated in the
runApplication function using a ConnectionPool. The ConnectionPool is
created in the main function and passed in where runApplication is called.
*** Main.hs (Old)
--- Main.hs (New)
***************
*** 28,32 ****
! runApplication :: String -> Application a -> IO a
! runApplication greeting (Application io) =
let
context =
--- 28,37 ----
! runApplication :: O.ConnectionPool -> String -> Application a -> IO a
! runApplication pool greeting (Application io) =
let
+ orvilleState =
+ O.newOrvilleState
+ O.defaultErrorDetailLevel
+ pool
+
context =
***************
*** 34,35 ****
--- 39,41 ----
{ applicationGreeting = greeting
+ , applicationOrvilleState = orvilleState
}
***************
*** 44,46 ****
main :: IO ()
! main =
! runApplication "Hello Application" myApplication
--- 50,62 ----
main :: IO ()
! main = do
! pool <-
! O.createConnectionPool
! O.ConnectionOptions
! { O.connectionString = "host=localhost user=postgres password=postgres"
! , O.connectionNoticeReporting = O.DisableNoticeReporting
! , O.connectionPoolStripes = O.OneStripePerCapability
! , O.connectionPoolLingerTime = 10
! , O.connectionPoolMaxConnections = O.MaxConnectionsPerStripe 1
! }
!
! runApplication pool "Hello Application" myApplicationNow we must declare an instance of HasOrvilleState to allow Orville access to
the OrvilleState state that is stored in our custom ApplicationContext
field. The askOrvilleState function is generally quite easy to implement.
It’s the equivalent of the ask function from the Reader module. In this
example we use the asks function from the Reader module to access the
applicationOrvilleState field in the reader context.
The localOrvilleState function is the equivalent of the local function from
the Reader module. It’s slightly more complicated to implemented because we
have adapt the function that Orville passes to localOrvilleState (which has type
OrvilleState -> OrvilleState) so that the function is applied within the
ApplicationContext. The adapted function is then passed Reader.local to
complete our implementation of localOrvilleState. We’ve included a type
signature for mkLocalContext in the example so you can clearly see the type
of function being passed to Reader.local, but this is not necessary for the
code to compile.
*** Main.hs (Old)
--- Main.hs (New)
***************
*** 24,25 ****
--- 24,39 ----
+ instance O.HasOrvilleState Application where
+ askOrvilleState =
+ Application (Reader.asks applicationOrvilleState)
+
+ localOrvilleState f (Application reader) =
+ let
+ mkLocalContext :: ApplicationContext -> ApplicationContext
+ mkLocalContext ctx =
+ ctx
+ { applicationOrvilleState = f (applicationOrvilleState ctx)
+ }
+ in
+ Application (Reader.local mkLocalContext reader)
+
getGreeting :: Application StringOnce we have defined our instance of HasOrvilleState we can add
MonadOrville and MonadOrvilleControl to the list of derived instances for
Application.
*** Main.hs (Old)
--- Main.hs (New)
***************
*** 22,23 ****
--- 22,25 ----
, MIO.MonadIO
+ , O.MonadOrville
+ , O.MonadOrvilleControl
)Now our Application monad is fully equipped with Orville capabilities! The
previous guide showed how
to add a first table and Orville operation as well. That part is exactly the
same from this point, so we won’t include it again here. We’ll conclude this
guide with the final listing of Main.hs with all our changes applied.
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Main
( main
) where
import qualified Control.Monad.IO.Class as MIO
import qualified Control.Monad.Reader as Reader
import qualified Orville.PostgreSQL as O
data ApplicationContext =
ApplicationContext
{ applicationGreeting :: String
, applicationOrvilleState :: O.OrvilleState
}
newtype Application a =
Application (Reader.ReaderT ApplicationContext IO a)
deriving
( Functor
, Applicative
, Monad
, MIO.MonadIO
, O.MonadOrville
, O.MonadOrvilleControl
)
instance O.HasOrvilleState Application where
askOrvilleState =
Application (Reader.asks applicationOrvilleState)
localOrvilleState f (Application reader) =
let
mkLocalContext :: ApplicationContext -> ApplicationContext
mkLocalContext ctx =
ctx
{ applicationOrvilleState = f (applicationOrvilleState ctx)
}
in
Application (Reader.local mkLocalContext reader)
getGreeting :: Application String
getGreeting =
Application (Reader.asks applicationGreeting)
runApplication :: O.ConnectionPool -> String -> Application a -> IO a
runApplication pool greeting (Application io) =
let
orvilleState =
O.newOrvilleState
O.defaultErrorDetailLevel
pool
context =
ApplicationContext
{ applicationGreeting = greeting
, applicationOrvilleState = orvilleState
}
in
Reader.runReaderT io context
myApplication :: Application ()
myApplication = do
greeting <- getGreeting
MIO.liftIO . putStrLn $ greeting
main :: IO ()
main = do
pool <-
O.createConnectionPool
O.ConnectionOptions
{ O.connectionString = "host=localhost user=postgres password=postgres"
, O.connectionNoticeReporting = O.DisableNoticeReporting
, O.connectionPoolStripes = O.OneStripePerCapability
, O.connectionPoolLingerTime = 10
, O.connectionPoolMaxConnections = O.MaxConnectionsPerStripe 1
}
runApplication pool "Hello Application" myApplication