这个问题与groundhog
或persistent
有关,因为我认为两者都有同样的问题。
假设我有一个转换器Tr m a
,它提供了一些功能f :: Int -> Tr m ()
。此功能需要数据库访问。我可以在这里使用一些选项,但没有一个是令人满意的。
我可以在DbPersist
内的某处放置Tr
变换器。实际上,我需要将它放在顶部,因为标准变换器没有PersistBackend
个实例,我仍然需要为我的Tr
newtype编写一个实例。这已经很糟糕,因为这个课程远没有那么简单。我也可以解除我做的所有数据库操作。
另一个选项是将f
的签名更改为PersistBackend m => Int -> Tr m ()
。这将再次要求PersistBackend
新类型上的Tr
实例或提升。
现在这是真正的问题。如何在已经具有Tr
约束的上下文中运行PersistBackend
?没有办法与Tr
分享。
我可以执行第一个选项并使用一些新的连接池在DbPersist
内运行实际的Tr
转换器(据我所知,没有办法从{{{{{{{ 1}}上下文我已经在,或者我可以做第二个选项并使run函数为PersistBackend
。第二个选项实际上是完全正常的,但问题是最终必须在堆栈中的某个地方的runTr :: PersistBackend m => Tr m a -> m a
现在位于DbPersist
变换器下并且没有{{1标准变换器的实例,其中Tr
由。{/ p>组成
这里的正确方法是什么?目前似乎最好的选择是在堆栈中的某个地方使用sepatare PersistBackend
,根据请求为我提供连接池,然后在我想要访问的任何地方对{池}进行Tr
DB。看看ReaderT
基本上已经只是runDbConn
我不明白必须这样做。
答案 0 :(得分:3)
我建议使用master
分行的latest groundhog。即使我将要描述的更改似乎已于2015年9月实施,但没有任何版本已经发布到Hackage。但作者似乎已经解决了这个问题。
在提示上,PersistBackend
现在是一个更简单的实施类,远远超过曾经的几十个方法长的庞然大物:
class (Monad m, Applicative m, Functor m, MonadIO m, ConnectionManager (Conn m), PersistBackendConn (Conn m)) => PersistBackend m where
type Conn m
getConnection :: m (Conn m)
instance (Monad m, Applicative m, Functor m, MonadIO m, PersistBackendConn conn) => PersistBackend (ReaderT conn m) where
type Conn (ReaderT conn m) = conn
getConnection = ask
他们为ReaderT conn m
写了一个实例(DbPersist
已被弃用,别名为ReaderT conn
),如果你选择去,你可以轻松地为Tr (ReaderT conn)
写一个把ReaderT
放在里面而不是外面的路线。它不是一个mtl
monad变换器,因为你必须实例Tr m
而不是Tr
,但是这和他们使用的相关数据类型技巧应该允许你使用自定义monad堆叠没有太大的麻烦。
您选择的任何一个选项都可能需要一些提升。在我个人看来,我会把ReaderT conn
放在堆栈的外面。这样,mtl
助手仍然可以抬起你的大部分堆叠,你可以粘上一个额外的升降机把它带回家。并且,如果您坚持使用Hackage上的版本,这似乎是唯一合理的选择,否则您将拥有(旧的)单片PersistBackend
类。
持久性稍微简单一点:只要monad变换器堆栈包含ReaderT SqlBackend
并终止于IO
,您就可以调用runSqlPool :: MonadBaseControlIO m => ReaderT SqlBackend m a -> Pool SqlBackend -> m a
。所有持久化操作都被定义为返回ReaderT backend m a
类型的东西,因此设计类型就可以解决了。