gpt4 book ai didi

haskell - 何时(何时不)定义 Monad

转载 作者:IT王子 更新时间:2023-10-29 05:55:05 26 4
gpt4 key购买 nike

这是一个与 API 设计实践相关的问题,用于为 Haskell 库定义您自己的 Monad 实例。定义 Monad 实例似乎是隔离 DSL 的好方法,例如Par monad in monad-par, hdph; Process 在分布式进程中; Eval 并行等...

我举了两个 haskell 库的例子,它们的目的是与数据库后端进行 IO。我举的例子是riak对于 Riak IO,和 hedis用于 Redis IO。

在 hedis 中,一个 Redis monad is defined .从那里,您使用 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 包引入了一个 monad Redis 以使用 runRedis 封装与 redis 的 IO 操作。相比之下,withConnection 中的 riak 包只接受一个接受 Connection 的函数,并在 IO monad 中执行它。

那么,定义您自己的 Monad 实例和 Monad 堆栈的动机是什么?为什么 riak 和 redis 包对此的处理方式不同?

最佳答案

对我来说,这一切都是关于封装和保护用户免受 future 实现变更的影响。正如 Casey 所指出的,这两个现在大致相同——基本上是一个 Reader Connection monad。但想象一下,这些将如何表现受制于 future 的不确定变化。如果两个包最终都决定用户需要状态 monad 接口(interface)而不是阅读器怎么办?如果发生这种情况,riak 的 withConnection 函数将更改为这样的类型签名:

withConnection :: Pool -> (Connection -> IO (a, Connection)) -> IO a

这将需要对用户代码进行全面更改。但是 Redis 包可以在不影响用户的情况下实现这样的改变。

现在,有人可能会争辩说,这种假设情况非常不现实,不需要您进行计划。在这两个特殊情况下,这可能是正确的。但是所有的项目都会随着时间的推移而发展,而且经常以无法预料的方式发展。定义您自己的 monad 允许您向用户隐藏内部实现细节,并提供一个在未来更改时更加稳定的接口(interface)。

这样说时,有些人可能会得出结论,定义自己的 monad 是更好的方法。但我认为情况并非总是如此。 (lens 库作为一个潜在的好反例浮现在脑海中。)定义一个新的 monad 是有代价的。如果您正在使用 monad 转换器,它可能会造成性能损失。在其他情况下,API 可能最终变得更加冗长。 Haskell 非常好,可以让您保持语法非常精简,在这种特殊情况下,差异不是很大——可能是一些 liftIO 用于 redis 和一些 lambda 用于 riak。

软件设计很少被剪切和干燥。您很少能够自信地说出何时何地不定义自己的 monad。但我们可以意识到所涉及的权衡,以帮助我们在遇到个别情况时对其进行评估。

关于haskell - 何时(何时不)定义 Monad,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16065462/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com