Я работаю на сервере Haskell, используя scotty
и persistent
. Многие обработчики нуждаются в доступе к пулу подключений к базе данных, поэтому я решил передать пул по всему приложению таким образом:
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
где getPeople
и getFoods
являются подходящими действиями базы данных persistent
, которые возвращают [Person]
и [Food]
соответственно.
Образ вызова liftIO
и runSqlPool
в пуле становится утомительным через некоторое время - не было бы здорово, если бы я мог реорганизовать их в одну функцию, например Yesod runDB
, которая просто взяла бы запросить и вернуть соответствующий тип. Моя попытка написать что-то вроде этого:
runDB' :: (MonadIO m) => ConnectionPool -> SqlPersistT IO a -> m a
runDB' pool q = liftIO $ runSqlPool q pool
Теперь я могу написать это:
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
За исключением того, что GHC жалуется:
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'
Кажется, что GHC говорит, что на самом деле тип runDB
становится каким-то образом специализированным. Но тогда как определяются функции, такие как runSqlPool
? Его подпись типа похожа на мою:
runSqlPool :: MonadBaseControl IO m => SqlPersistT m a -> Pool Connection -> m a
но он может использоваться с запросами базы данных, которые возвращают много разных типов, как я делал изначально. Я думаю, что есть что-то фундаментальное, я неправильно понимаю типы здесь, но я не знаю, как узнать, что это такое! Любая помощь будет принята с благодарностью.
EDIT:
по предложению Юраса, я добавил следующее:
type DBRunner m a = (MonadIO m) => SqlPersistT IO a -> m a
runDB' :: ConnectionPool -> DBRunner m a
app :: forall a. DBRunner ActionM a -> ScottyM ()
для которого требуется -XRankNTypes
для typedef. Однако ошибка компилятора по-прежнему идентична.
EDIT:
Победа комментаторам. Это позволяет компилировать код:
app :: (forall a. DBRunner ActionM a) -> ScottyM ()
За что я благодарен, но все еще озадачен!