Rob*_*art 14 haskell redis riak
这是一个与用于为Haskell库定义自己的Monad实例的API设计实践相关的问题.定义Monad实例似乎是隔离DSL的好方法,例如Parmonad-par中的monad,hdph; Process在分布式过程中; Eval并行等...
我拿两个haskell库的例子,其目的是使用数据库后端的IO.我采取的例子了Riak对了Riak IO和HEDIS对Redis的IO.
在hedis中,定义了Redismonad .从那里,您使用redis运行IO:
data Redis a -- instance Monad Redis
runRedis :: Connection -> Redis a -> IO a
class Monad m => MonadRedis m
class MonadRedis m => RedisCtx m f | m -> f
set :: RedisCtx m f => ByteString -> ByteString -> m (f Status)
example = do
conn <- connect defaultConnectInfo
runRedis conn $ do
set "hello" "world"
world <- get "hello"
liftIO $ print world
Run Code Online (Sandbox Code Playgroud)
在riak中,事情是不同的:
create :: Client -> Int -> NominalDiffTime -> Int -> IO Pool
ping :: Connection -> IO ()
withConnection :: Pool -> (Connection -> IO a) -> IO a
example = do
conn <- connect defaultClient
ping conn
Run Code Online (Sandbox Code Playgroud)
说明文件runRedis:"runRedis的每次调用都从连接池获取网络连接并运行给定的Redis操作.因此,当来自池的所有连接都在使用时,对runRedis的调用可能会阻塞." .但是,riak包还实现了连接池.这是在IO monad之上没有额外的monad实例的情况下完成的:
create :: Client-> Int -> NominalDiffTime -> Int -> IO Pool
withConnection :: Pool -> (Connection -> IO a) -> IO a
exampleWithPool = do
pool <- create defaultClient 1 0.5 1
withConnection pool $ \conn -> ping conn
Run Code Online (Sandbox Code Playgroud)
因此,两个软件包之间的类比归结为这两个函数:
runRedis :: Connection -> Redis a -> IO a
withConnection :: Pool -> (Connection -> IO a) -> IO a
Run Code Online (Sandbox Code Playgroud)
据我所知,hedis软件包引入了一个Redis使用redis封装IO动作的monad runRedis.相比之下,riak包withConnection只需要一个函数,它接受一个Connection并在IO monad中执行它.
那么,定义自己的Monad实例和Monad堆栈的动机是什么?为什么riak和redis包的方法不同?
mig*_*yte 10
对我而言,这完全取决于封装和保护用户免受未来实施变更的影响.正如凯西指出的那样,这两个现在大致相当 - 基本上是一个Reader Connection单子.但想象一下这些行为将如何受到不确定变化的影响.如果两个软件包最终决定用户需要状态monad接口而不是读者,该怎么办?如果发生这种情况,riak的withConnection函数将更改为类似签名,如下所示:
withConnection :: Pool -> (Connection -> IO (a, Connection)) -> IO a
Run Code Online (Sandbox Code Playgroud)
这将需要对用户代码进行彻底更改.但Redis软件包可以在不破坏用户的情况下实现这样的改变.
现在,有人可能会认为这个假设情景非常不现实,而且你不需要计划.在这两个特殊情况下,这可能是真的.但是所有项目都会随着时间的推移而发展,并且经常以不可预见的方式发展.定义自己的monad允许您隐藏用户的内部实现细节,并提供一个通过将来的更改更稳定的界面.
当这样陈述时,有些人可能会得出结论,定义自己的monad是优越的方法.但我不认为总是这样.(作为一个可能很好的反例,人们会想到镜头库.)定义一个新的monad会产生成本.如果你使用monad变换器,它可能会造成性能损失.在其他情况下,API最终可能会更加冗长.Haskell非常好,让你保持语法非常小,在这种特殊情况下,差异不是很大 - 可能是liftIOredis的几个和riak的一些lambdas.
软件设计很少被切割和干燥.很少有人能够自信地说何时何地不定义自己的monad.但是,我们可以意识到所涉及的权衡,以帮助我们在遇到它们时评估个别情况.