Haskell 如何将上下文和'IO'单子隐藏到另一个单子中?
我正在尝试使用Haskell 如何将上下文和'IO'单子隐藏到另一个单子中?,haskell,monads,Haskell,Monads,我正在尝试使用HDBC和Haskell.GI实现一个小型桌面应用程序。我使用glade构建窗口和对话框,并使用GtkBuilder加载它们。在实现了两个场景之后,我始终使用相同的模式,在do块中组成“动作”,其签名为: Connection -> Builder -> a -> IO b 这些“操作”是在IOmonad的上下文中组成的,主要问题是我必须将连接和构建器到处传递。我预见到的另一个问题是,如果我想向我的应用程序添加另一个外部依赖项(例如,访问图像扫描仪),我必须更改
HDBC
和Haskell.GI
实现一个小型桌面应用程序。我使用glade构建窗口和对话框,并使用GtkBuilder
加载它们。在实现了两个场景之后,我始终使用相同的模式,在do
块中组成“动作”,其签名为:
Connection -> Builder -> a -> IO b
这些“操作”是在IO
monad的上下文中组成的,主要问题是我必须将连接
和构建器
到处传递。我预见到的另一个问题是,如果我想向我的应用程序添加另一个外部依赖项(例如,访问图像扫描仪),我必须更改我所有“操作”的签名,更重要的是,更改它们的算术
我能做的是:我能定义一个类型同义词:
type Action a b = Connection -> Builder -> a -> IO b
我还可以创建一个命名元组来消除算术性问题:
data Context =
Context {
conn :: Connection,
builder :: Builder}
但是,这仍然不能解决这样一个事实:每次我想访问数据库时,我都必须调用(conn ctx)
,或者在每个操作中使用let
绑定
我觉得最理想的是制作我自己的monad,在其中我可以编写我的动作,我不会明确地谈论我的连接
或构建器
值
知道IO
已经是一个单子,我如何定义这样的单子呢
顺便说一句,它和状态
单子有什么关系吗
[…]主要的问题是我必须通过我的连接
和构建器
因此,这些都是您(反复)阅读的“环境”的一部分。这就是阅读器的作用。包mtl
包含monad transformer,它为基本monad添加了读卡器功能,在您的例子中是IO
演示:
假设一个简单的动作,比如
no_action :: Connection -> Builder -> Int -> IO Int
no_action _ _ i = return (i + 1)
您可以将其放入一个新的Monad中,该Monad类似于IO
,但通过定义上下文并应用Monad转换器,可以访问连接和生成器:
data Context = Context { connection :: Connection
, builder :: Builder }
type CBIO b = ReaderT Context IO b
将您的动作提升到这个新的(组合的)单子中,应该有一个单独的功能:
liftCBIO :: (Connection -> Builder -> a -> IO b) -> (a -> CBIO b)
liftCBIO f v = do
context <- ask
liftIO (f (connection context) (builder context) v)
。。。和cbio\u no\u action num
要实际运行新的monad,您需要使用runReaderT
。。但这也应该有一个更好的名字:
runWithInIO = flip runReaderT
如果愿意,您还可以将其更改为包含构建上下文
使用上面的方法看起来是这样的:
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
main=do
听起来你可能想看看读卡器monad。(它确实类似于状态单子,但状态是只读的。)@RobinZigmond:好的。你的意思似乎是:如果我像问题中那样定义上下文
类型,并定义类型Action=ReaderT Context IO
,我可以做(reader conn)
,(reader builder)
和liftIO$do…
?不完全-(ReaderT Context IO)b
,模新类型
包装,相当于Context->IO b
——因此它不包括a
参数。当然,您可以重新排列参数的顺序并使用函数a->ReaderT Context IO b
,这样(新)类型Action a b=a->ReaderT Context IO b
将是一个合适的单元可组合函数类型。(monad是ReaderT Context IO
)我想我现在明白了。使用您自己的monad,您可以定义自己的助手,如getConnection::Action a Connection
。您可能还想定义一个MonadIO(Action a)
实例,这样您就可以在自定义monad中运行liftIO(putStrLn“hello”)
。
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