This is a question relating to the API design practises for defining your own Monad instances for Haskell libraries. Defining Monad instances seems to be a good way to isolate DSL's e.g. Par
monad in monad-par, hdph; Process
in distributed-process; Eval
in parallel etc...
I take two examples of haskell libraries, whose purpose is to IO with database backends. The examples I take are riak for Riak IO, and hedis for Redis IO.
In hedis, a Redis
monad is defined. From there, you run IO with redis as:
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
In riak, things are different:
create :: Client -> Int -> NominalDiffTime -> Int -> IO Pool
ping :: Connection -> IO ()
withConnection :: Pool -> (Connection -> IO a) -> IO a
example = do
conn <- connect defaultClient
ping conn
The documentation for runRedis
says: "Each call of runRedis takes a network connection from the Connection pool and runs the given Redis action. Calls to runRedis may thus block while all connections from the pool are in use.". However, the riak package also implements connection pools. This is done without additional monad instances on top of the IO 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
So, the analogy between the two packages boils down to these two functions:
runRedis :: Connection -> Redis a -> IO a
withConnection :: Pool -> (Connection -> IO a) -> IO a
As far as I can tell, the hedis package introduces a monad Redis
to encapsulate IO actions with redis using runRedis
. In contrast the riak package in withConnection
simply takes a function that takes a Connection
, and executes it in the IO monad.
So, what are the motivations for defining your own Monad instances and Monad stacks? Why has the riak and redis packages differed in their approach to this?