Ich arbeite an einem Haskell-Server mit scotty
und persistent
. Viele Handler benötigen Zugriff auf den Datenbankverbindungspool, also hat mich genommen, den Pool, das um überall in der App in dieser Art der Mode:Wann ist eine generische Funktion nicht generisch?
main = do
runNoLoggingT $ withSqlitePool ":memory:" 10 $ \pool ->
liftIO $ scotty 7000 (app pool)
app pool = do
get "/people" $ do
people <- liftIO $ runSqlPool getPeople pool
renderPeople people
get "/foods" $ do
food <- liftIO $ runSqlPool getFoods pool
renderFoods food
wo getPeople
und getFoods
sind entsprechende persistent
Datenbankaktionen, die Rückkehr [Person]
bzw. [Food]
.
Das Muster liftIO
und runSqlPool
auf einen Pool von Aufruf nach einer Weile ermüdend wird - wäre es nicht toll, wenn ich sie in einer einzigen Funktion Refactoring könnte, wie Yesod des runDB
, die nur die Abfrage nehmen würde und geben die entsprechenden Art. Mein Versuch, so etwas wie dieses zu schreiben ist:
runDB' :: (MonadIO m) => ConnectionPool -> SqlPersistT IO a -> m a
runDB' pool q = liftIO $ runSqlPool q pool
Jetzt kann ich schreiben:
main = do
runNoLoggingT $ withSqlitePool ":memory:" 10 $ \pool ->
liftIO $ scotty 7000 $ app (runDB' pool)
app runDB = do
get "/people" $ do
people <- runDB getPeople
renderPeople people
get "/foods" $ do
food <- runDB getFoods
renderFoods food
Außer, dass GHC klagt:
Couldn't match type `Food' with `Person'
Expected type: persistent-2.1.1.4:Database.Persist.Sql.Types.SqlPersistT
IO
[persistent-2.1.1.4:Database.Persist.Class.PersistEntity.Entity
Person]
Actual type: persistent-2.1.1.4:Database.Persist.Sql.Types.SqlPersistT
IO
[persistent-2.1.1.4:Database.Persist.Class.PersistEntity.Entity
Food]
In the first argument of `runDB', namely `getFoods'
Es scheint wie GHC sagt, dass in der Tat der Typ von runDB
irgendwie spezialisiert wird. Aber wie sind dann Funktionen wie runSqlPool
definiert? Seine Art Signatur sieht ähnlich wie mir:
runSqlPool :: MonadBaseControl IO m => SqlPersistT m a -> Pool Connection -> m a
aber es kann mit Datenbankabfragen verwendet werden, die viele verschiedene Arten zurückkehren, wie ich ursprünglich tat. Ich denke, es gibt etwas Grundlegendes, dass ich hier Typen falsch verstehe, aber ich habe keine Ahnung, wie ich herausfinden soll, was es ist! Jede Hilfe würde sehr geschätzt werden.
EDIT:
bei Yuras' Vorschlag, ich habe das hinzugefügt:
type DBRunner m a = (MonadIO m) => SqlPersistT IO a -> m a
runDB' :: ConnectionPool -> DBRunner m a
app :: forall a. DBRunner ActionM a -> ScottyM()
die -XRankNTypes
für die typedef erforderlich. Der Compilerfehler ist jedoch immer noch identisch.
EDIT:
Sieg der commentors. Dies ermöglicht den Code zu kompilieren:
app :: (forall a. DBRunner ActionM a) -> ScottyM()
Für die ich dankbar bin, aber immer noch mystifiziert!
Der Code sieht derzeit wie this und this aus.
Versuchen Sie, Typ-Signatur zu 'app' mit expliziten' forall' hinzuzufügen und wahrscheinlich würden Sie sehen, was falsch ist. – Yuras
@Yuras Ich denke, ein Teil dieses Problems ist, dass ich derzeit ein sehr schlechtes Verständnis von expliziten "Forall" habe, aber ich werde mich bemühen, dies zu tun. –
Ich denke, es sollte "app :: (forall a. DBRunner AktionM a) -> ScottyM()". –