【发布时间】:2017-06-29 17:03:31
【问题描述】:
我想尽可能地限制程序中函数的影响,以便 例如如果我有一个应该查询数据库的函数,我知道它不会 打印用于删除我的文件的东西。
作为一个具体的例子,假设我有一个带有“用户”表的数据库。
有些函数只读取这张表,有些函数读写。
使用 mtl 和转换器,我可以尝试这样的事情:
data User = User { username :: String }
deriving (Show)
class Monad m => ReadDb m where
getUsers :: m [User]
getUserByName :: String -> m (Maybe User)
class Monad m => WriteDb m where
addUser :: String -> m ()
removeUser :: String -> m Bool
但是,实现我需要的实例很棘手,如果不是不可能的话。成为
能够访问我需要SqlBackend 和 IO 的数据库:
data SqlBackend
instance (MonadReader SqlBackend m, MonadIO m, Monad m) => ReadDb m where
getUsers = undefined
getUserByName = undefined
instance (MonadReader SqlBackend m, MonadIO m, Monad m) => WriteDb m where
addUser = undefined
removeUser = undefined
使用UndecidableInstances 可以正常工作。但是,假设我也需要
记录,不,我不会在 [String] 或类似的东西中收集日志字符串
那。记录器应该有效地记录,并且记录消息应该出现在
实时。
所以我可以这样做:
class Monad m => Log m where
log :: String -> m ()
日志需要Logger,所以我可以定义一个类似的实例
data Logger
instance (MonadReader Logger m, MonadIO m, Monad m) => Log m where
log = undefined
现在读取数据库和日志的函数如下所示:
logUsers :: (ReadDb m, Log m) => m ()
logUsers = getUsers >>= log . show
但不幸的是,我无法真正运行它,因为我需要提供
MonadReader SqlBackend m 和 MonadReader Logger m,这是不可能的
因为函数依赖MonadReader r m | m -> r。
有一些解决方法(比如实现一个不同的类型类只是为了得到
Logger 和 SqlBackend),但它们涉及的样板太多。
作为替代方案,我想尝试 Oleg 的可扩展效果库(Eff
monad,在这里实现http://okmij.org/ftp/Haskell/extensible/Eff.hs)。这
据我了解,麻烦是需要处理的多种效果
IO 无法在Eff 中以可组合的方式实现。例如,Trace
库中的效果是这样实现的:
data Trace
runTrace :: Eff (Trace :> Void) w -> IO w
Void 部分是这里的问题。在我的示例中,我想处理读取、写入和
单独记录操作,并且功能应该能够具有
允许这些效果的任何子集的细粒度类型。
这里想到了Free,但我不确定如何定义函子
对于这些效果,然后组合它们,例如一个函数
logs 将能够调用另一个不记录但有其他功能的函数
效果一样。
所以我的问题是:如何在我的程序中获得细粒度的效果类型,
实际组成的效果处理程序。效果处理程序应该能够运行
IO。假设性能不是问题(所以Free 等是可以的)。
【问题讨论】:
标签: haskell