这是一个与为 Haskell 库定义自己的 Monad 实例的 API 设计实践相关的问题。定义 Monad 实例似乎是隔离 DSL 的好方法,例如Par
monad 在 monad-par、hdph;Process
在分布式进程中;Eval
并行等...
我举了两个 haskell 库的例子,它们的目的是与数据库后端进行 IO。我举的例子是Riak IO 的riak和 Redis IO 的hedis。
在 hedis 中,定义Redis
了一个monad 。从那里,您使用 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
在 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
文档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
所以,这两个包之间的类比归结为这两个功能:
runRedis :: Connection -> Redis a -> IO a
withConnection :: Pool -> (Connection -> IO a) -> IO a
据我所知,hedis 包引入了一个 monadRedis
来使用 redis 封装 IO 操作runRedis
。相比之下, riak 包withConnection
只接受一个接受 a 的函数Connection
,并在 IO monad 中执行它。
那么,定义自己的 Monad 实例和 Monad 堆栈的动机是什么?为什么 riak 和 redis 包的处理方法不同?