Created
October 14, 2022 20:53
-
-
Save tek/155728a934e574b50e41462016cac0b9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Conc (interpretScoped, interpretScopedWith, interpretSync) | |
import Database.PostgreSQL.Simple (Connection) | |
import qualified Database.PostgreSQL.Simple.Transaction as Psql | |
import Polysemy.Bundle (Bundle (Bundle), sendBundle) | |
import Polysemy.Internal.Kind (Append) | |
import Polysemy.Membership (ElemOf (Here, There)) | |
import qualified Sync | |
data Connections :: Effect where | |
New :: Connections m Connection | |
Release :: Connection -> Connections m () | |
makeSem ''Connections | |
-- call withConnectionPool here or something | |
interpretConnections :: InterpreterFor Connections r | |
interpretConnections = | |
undefined | |
data Pg a = Pg | |
data Database :: Effect where | |
Query :: Pg a -> Database m (Maybe a) | |
makeSem ''Database | |
transact :: | |
Member (Scoped res Database) r => | |
InterpreterFor Database r | |
transact = | |
scoped | |
runQuery :: Connection -> Pg a -> IO (Maybe a) | |
runQuery = | |
undefined | |
-- Each time 'Database' is scoped in 'storeScope', a new transaction is wrapped around the scoped computation | |
dbScope :: | |
Members [Connections, Resource, Embed IO] r => | |
(Connection -> Sem r a) -> | |
Sem r a | |
dbScope use = do | |
bracket new release \ conn -> do | |
onException | |
do | |
embed (Psql.begin conn) | |
use conn <* embed (Psql.commit conn) | |
do | |
embed (Psql.rollback conn) | |
interpretDatabase :: | |
Members [Connections, Resource, Embed IO] r => | |
InterpreterFor (Scoped Connection Database) r | |
interpretDatabase = | |
interpretScoped dbScope \ conn -> \case | |
Query q -> | |
embed (runQuery conn q) | |
data Store a :: Effect where | |
Fetch :: Store a m (Maybe [a]) | |
makeSem ''Store | |
-- bolierplate for 'Store' bundles | |
class InjectStores bundle stores r where | |
injectStores :: Sem (Append stores r) a -> Sem r a | |
instance InjectStores bundle '[] r where | |
injectStores = | |
id | |
instance ( | |
InjectStores bundle stores r, | |
Member store bundle, | |
Member (Bundle bundle) r, | |
Member (Bundle bundle) (Append stores r) | |
) => InjectStores bundle (store : stores) r where | |
injectStores = | |
injectStores @bundle @stores . sendBundle @store @bundle @(Append stores r) | |
-- For the convenience of not having to write @withStores [Store Creator, Store Reward]@ | |
type family StoreList (stores :: [Type]) :: EffectRow where | |
StoreList '[] = '[] | |
StoreList (store : stores) = Store store : StoreList stores | |
withStores :: | |
∀ stores r a . | |
InjectStores (StoreList stores) (StoreList stores) r => | |
Sem (Append (StoreList stores) r) a -> | |
Sem r a | |
withStores = | |
injectStores @(StoreList stores) @(StoreList stores) | |
class HandleStore e r where | |
handleStore :: e m a -> Sem (Database : r) a | |
class HandleStores stores r where | |
handleStores :: ElemOf e stores -> e m a -> Sem (Database : r) a | |
instance HandleStores '[] r where | |
handleStores = \case | |
instance ( | |
HandleStore store r, | |
HandleStores stores r | |
) => HandleStores (store : stores) r where | |
handleStores Here e = | |
handleStore e | |
handleStores (There pr) e = | |
handleStores @stores pr e | |
-- Creates a 'Database' scope within a 'Store' scope that starts a transaction. | |
storeScope :: | |
Member (Scoped res Database) r => | |
(() -> Sem (Database : r) a) -> | |
Sem r a | |
storeScope use = | |
transact do | |
use () | |
-- unwrap a store bundle and use the classes to dispatch to the correct handler | |
interpretStores :: | |
HandleStores stores r => | |
Member (Scoped res Database) r => | |
InterpreterFor (Scoped () (Bundle stores)) r | |
interpretStores = | |
interpretScopedWith @'[Database] storeScope \ _ (Bundle pr e) -> | |
handleStores pr e | |
----------------- | |
-- business logic | |
----------------- | |
data Creator = Creator | |
data Reward = Reward | |
endpoint :: | |
Members [Store Creator, Store Reward] r => | |
Sem r () | |
endpoint = | |
void (fetch @Creator) | |
runEndpoint :: | |
Member (Scoped res (Bundle [Store Creator, Store Reward])) r => | |
Sem r () | |
runEndpoint = | |
scoped do | |
withStores @[Creator, Reward] endpoint | |
instance HandleStore (Store Creator) r where | |
handleStore Fetch = | |
query Pg | |
instance HandleStore (Store Reward) r where | |
handleStore Fetch = | |
query Pg | |
main :: IO () | |
main = | |
runFinal $ | |
embedToFinal $ | |
resourceToIOFinal $ | |
interpretConnections $ | |
interpretDatabase $ | |
interpretStores runEndpoint |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Ooooh I see now, gotcha.