我正在尝试使用 HDBC
和 Haskell.GI
实现一个小型桌面应用程序。我使用 glade 构建窗口和对话框,并使用 GtkBuilder
加载它们。在实现了几个场景之后,我最终始终使用相同的模式,在 do
block 中编写“ Action ”,其签名为:
Connection -> Builder -> a -> IO b
这些“ Action ”在 IO
monad 的上下文中组合,主要问题是我必须传递我的 Connection
和 Builder
在周围。我预见的另一个问题是,如果我想向我的应用程序添加另一个外部依赖项(例如,访问图像扫描仪),我将不得不更改我所有“ Action ”的签名,更重要的是,它们的数量。
我能做什么:我可以定义一个类型同义词:
type Action a b = Connection -> Builder -> a -> IO b
我还可以创建一个命名元组来消除元数问题:
data Context =
Context {
conn :: Connection,
builder :: Builder}
但是,这仍然没有解决这样一个事实,即每次我想访问数据库时,我都必须调用 (conn ctx)
或使用 let
在每个 Action 中都有约束力。
我觉得最理想的是制作我自己的 monad,我可以在其中编写我的 Action ,我不会明确地谈论我的 Connection
或 Builder
值.
如果知道 IO
已经是一个 monad,我该如何定义一个这样的 monad?
顺便说一句,它与 State
monad 有什么关系吗?
最佳答案
[..] the main problem being that I have to pass my
Connection
andBuilder
all around.
所以这些是您(重复)阅读的“环境”的一部分。这就是 Reader
monad 的用途。 mtl
包包含 monad 转换器 ReaderT
它将阅读器功能添加到基本 monad,在您的例子中是 IO
。
演示:
假设一个简单的 Action ,比如..
no_action :: Connection -> Builder -> Int -> IO Int
no_action _ _ i = return (i + 1)
您可以将它放入一个类似于 IO
的新 Monad 中,但可以通过定义 Context
并应用 monad 转换器来访问连接和构建器:
data Context = Context { connection :: Connection
, builder :: Builder }
type CBIO b = ReaderT Context IO b
将你的 Action 提升到这个新的(组合的)monad 中值得一个单独的功能:
liftCBIO :: (Connection -> Builder -> a -> IO b) -> (a -> CBIO b)
liftCBIO f v = do
context <- ask
liftIO (f (connection context) (builder context) v)
然后你可以总是写(liftCBIO no_action) num
或者...
cbio_no_action = liftCBIO no_action
...和cbio_no_action num
。
要实际运行您的新 monad,您可以使用 runReaderT
.. 但这也值得一个更好的名称:
runWithInIO = flip runReaderT
如果您愿意,您也可以更改它以合并构建Context
。
使用上面的代码看起来像这样:
main = do
i <- runWithInIO (Context Connection Builder) $ do
a <- cbio_no_action 20
liftIO $ putStrLn "Halfway through!"
b <- cbio_no_action 30
return (a + b)
putStrLn $ show i
关于haskell - 如何将上下文和 `IO` monad 隐藏到另一个 monad 中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55706425/