- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我想尽可能地限制程序中函数的影响,以便例如如果我有一个应该查询数据库的函数,我知道它不会打印用于删除我的文件的内容。
作为一个具体示例,假设我有一个带有“用户”表的数据库。
有些函数只能读取该表,有些函数可以读写。
有了 mtl 和 Transformer,我可以尝试这样的事情:
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 的可扩展效果库(Effmonad,在这里实现 http://okmij.org/ftp/Haskell/extensible/Eff.hs )。这据我了解,麻烦在于需要处理多种影响IO 无法在 Eff
中以可组合的方式实现。例如,跟踪
库中的效果实现如下:
data Trace
runTrace :: Eff (Trace :> Void) w -> IO w
Void
部分是这里的问题。在我的例子中,我想处理读、写和单独记录操作,并且功能应该能够具有允许这些效果的任何子集的细粒度类型。
这里想到的一件事是Free
,但我不确定如何定义仿函数对于这些效果,然后组合它们,例如一个函数日志将能够调用另一个不记录但有其他功能的函数效果相同。
所以我的问题是:如何在我的程序中获得细粒度的效果类型,实际组成的效果处理程序。效果处理程序应该能够运行IO。假设性能不是问题(因此免费
等就可以了)。
最佳答案
我认为您的实例
声明是一个错误。
instance (MonadReader SqlBackend m, MonadIO m, Monad m) => ReadDb m
此实例将匹配所有类型构造函数m::* -> *
,然后如果相关的m
不匹配,则稍后失败。 t 适合实例上下文。实例搜索中没有回溯。换句话说,您无法更改 ReadDb 的实例(例如,如果您需要在测试期间模拟数据库)。它还会导致父类(super class)重叠的问题。
最好将程序构建为 monad 转换器堆栈,像往常一样使用 newtype
。所以我要写一个自定义的 monad 转换器:
data SqlConfig = SqlConfig { connectionString :: String }
newtype DbT m a = DbT (ReaderT SqlConfig m a) deriving (
Functor,
Applicative,
Alternative,
Monad,
MonadTrans,
MonadPlus,
MonadFix,
MonadIO,
MonadWriter w,
MonadState s,
MonadError e,
MonadCont
)
runDbT :: DbT m a -> SqlConfig -> m a
runDbT (DbT m) = runReaderT m
我正在使用 GeneralizedNewtypeDeriving
派生 mtl
类(MonadReader
除外)。 (这些实例还需要 UndecidableInstances,因为它们未满足覆盖条件。)我不想从DbT
,我想将它从基础 monad 中取出。 DbT
不是 ReaderT
,它只是碰巧使用 ReaderT
实现的。
mapDbT :: (m a -> n b) -> DbT m a -> DbT n b
mapDbT f (DbT m) = DbT $ mapReaderT f m
instance MonadReader r m => MonadReader r (DbT m) where
ask = lift ask
local = mapDbT . local
只要我们能够访问 IO
,我就可以使用 DbT
实现您的类:
instance MonadIO m => MonadReadDb (DbT m) where
getUsers = DbT $ ask >>= (liftIO . query "select * from Users")
getUserByName name = DbT $ ask >>= (liftIO . query "select * from Users where Name = @name")
instance MonadIO m => MonadWriteDb (DbT m) where
addUser u = DbT $ ask >>= (liftIO . query "insert Users (Name) values @name")
removeUser u = DbT $ ask >>= (liftIO . query "delete Users where Name = @name")
同样,我可以设置一个日志记录 monad 转换器:
data LoggingConfig = LoggingConfig { filePath :: String }
newtype LoggerT m a = LoggerT (ReaderT LoggingConfig m a) deriving (
Functor,
Applicative,
Alternative,
Monad,
MonadTrans,
MonadPlus,
MonadFix,
MonadIO,
MonadWriter w,
MonadState s,
MonadError e,
MonadCont
)
runLoggerT :: LoggerT m a -> LoggingConfig -> m a
runLoggerT (LoggerT m) = runReaderT m
instance MonadIO m => MonadLogger (LoggerT m) where
log msg = LoggerT $ do
config <- ask
liftIO $ writeFile (filePath config) msg
-- MonadReader instance omitted. It's identical to the DbT instance
令人烦恼的是 - 这是 mtl
方法的主要缺点 - 您必须编写 O(n^2) 实例才能使这些类型很好地组合。
instance MonadLogger m => MonadLogger (DbT m) where
log = lift . log
instance MonadReadDb m => MonadReadDb (LoggerT m) where
getUsers = lift getUsers
getUserByName = lift . getUserByName
instance MonadWriteDb m => MonadWriteDb (LoggerT m) where
addUser = lift . addUser
removeUser = lift . removeUser
-- and a bunch of identical instances for all the types in transformers
您可以像往常一样使用三个类编写一元程序:
myProgram :: (MonadLogger m, MonadReadDb m, MonadWriteDb m) => m ()
myProgram = do
us <- getUsers
log $ "removing " ++ show (length us) ++ " users"
void $ traverse removeUser us
然后在程序的入口点,当您构建并运行 monad 转换器堆栈时,您只需解开 LoggerT
和 DbT
新类型并提供所需的配置。
runProgram :: LoggerT (DbT IO) a -> LoggingConfig -> SqlConfig -> IO a
runProgram m l s = runDbT (runLoggerT m l) s
ghci> :t runProgram myProgram
runProgram myProgram :: LoggingConfig -> SqlConfig -> IO ()
关于haskell - Eff 中的多个 IO 效果(或其他可组合效果的方式),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42174785/
我一直在阅读有关汇编函数的内容,但对于是使用进入和退出还是仅使用调用/返回指令来快速执行,我感到很困惑。一种方式快而另一种方式更小吗?例如,在不内联函数的情况下,在汇编中执行此操作的最快(stdcal
我正在处理一个元组列表,如下所示: res = [('stori', 'JJ'), ('man', 'NN'), ('unnatur', 'JJ'), ('feel', 'NN'), ('pig',
最近我一直在做很多网络或 IO 绑定(bind)操作,使用线程有助于加快代码速度。我注意到我一直在一遍又一遍地编写这样的代码: threads = [] for machine, user, data
假设我有一个名为 user_stats 的资源,其中包含用户拥有的帖子、评论、喜欢和关注者的数量。是否有一种 RESTful 方式只询问该统计数据的一部分(即,对于 user_stats/3,请告诉我
我有一个简单的 api,它的工作原理是这样的: 用户创建一个请求 ( POST /requests ) 另一个用户检索所有请求 ( GET /requests ) 然后向请求添加报价 ( POST /
考虑以下 CDK Python 中的示例(对于这个问题,不需要 AWS 知识,这应该对基本上任何构建器模式都有效,我只是在这个示例中使用 CDK,因为我使用这个库遇到了这个问题。): from aws
Scala 中管理对象池的首选方法是什么? 我需要单线程创建和删除大规模对象(不需要同步)。在 C++ 中,我使用了静态对象数组。 在 Scala 中处理它的惯用和有效方法是什么? 最佳答案 我会把它
我有一个带有一些内置方法的类。这是该类的抽象示例: class Foo: def __init__(self): self.a = 0 self.b = 0
返回和检查方法执行的 Pythonic 方式 我目前在 python 代码中使用 golang 编码风格,决定移动 pythonic 方式 例子: import sys from typing imp
我正在开发一个 RESTful API。其中一个 URL 允许调用者通过 id 请求特定人员的记录。 返回该 id 不存在的记录的常规值是什么?服务器是否应该发回一个空对象或者一个 404,或者其他什
我正在使用 pathlib.Path() 检查文件是否存在,并使用 rasterio 将其作为图像打开. filename = pathlib.Path("./my_file-name.tif") 但
我正在寻找一种 Pythonic 方式来从列表和字典创建嵌套字典。以下两个语句产生相同的结果: a = [3, 4] b = {'a': 1, 'b': 2} c = dict(zip(b, a))
我有一个正在操裁剪理设备的脚本。设备有时会发生物理故障,当它发生时,我想重置设备并继续执行脚本。我有这个: while True: do_device_control() device
做组合别名的最pythonic和正确的方法是什么? 这是一个假设的场景: class House: def cleanup(self, arg1, arg2, kwarg1=False):
我正在开发一个小型客户端服务器程序来收集订单。我想以“REST(ful)方式”来做到这一点。 我想做的是: 收集所有订单行(产品和数量)并将完整订单发送到服务器 目前我看到有两种选择: 将每个订单行发
我知道在 Groovy 中您可以使用字符串调用类/对象上的方法。例如: Foo."get"(1) /* or */ String meth = "get" Foo."$meth"(1) 有没有办法
在 ECMAScript6 中,您可以使用扩展运算符来解构这样的对象 const {a, ...rest} = obj; 它将 obj 浅拷贝到 rest,不带属性 a。 有没有一种干净的方法可以在
我有几个函数返回数字或None。我希望我的包装函数返回第一个不是 None 的结果。除了下面的方法之外,还有其他方法吗? def func1(): return None def func2(
假设我想设计一个 REST api 来讨论歌曲、专辑和艺术家(实际上我就是这样做的,就像我之前的 1312414 个人一样)。 歌曲资源始终与其所属专辑相关联。相反,专辑资源与其包含的所有歌曲相关联。
这是我认为必须经常出现的问题,但我一直无法找到一个好的解决方案。假设我有一个函数,它可以作为参数传递一个开放资源(如文件或数据库连接对象),或者需要自己创建一个。如果函数需要自己打开文件,最佳实践通常
我是一名优秀的程序员,十分优秀!