我正在尝试使用HDBC
和Haskell.GI
实现一个小型桌面应用程序。我使用林间空地构建窗口和对话框,并使用GtkBuilder
加载它们。在实施了几种方案之后,我最终在整个过程中使用了相同的模式,在do
块中编写了具有以下特征的“动作”:
Connection -> Builder -> a -> IO b
这些“动作”是在IO
单子的上下文中构成的,主要问题是我必须四处传递Connection
和Builder
。我预见到的另一个问题是,如果我想向应用程序添加另一个外部依赖项(例如,访问图像扫描器),则必须更改所有“动作”的签名,更重要的是,更改它们的关联性。
我能做什么:我可以定义类型同义词:
type Action a b = Connection -> Builder -> a -> IO b
我还可以创建一个命名元组来消除arity问题:
data Context =
Context {
conn :: Connection,
builder :: Builder}
但是,这仍然不能解决以下事实:每次我要访问数据库时,我都必须在每个操作中调用(conn ctx)
或使用let
绑定。
我认为理想的情况是制作自己的monad,在其中可以构成自己的动作,而我不会明确谈论我的Connection
或Builder
值。
知道IO
已经是单子之后,我将如何定义这样的单子?
顺便说一句,与State
单子有什么关系?
答案 0 :(得分:1)
[..] the main problem being that I have to pass my
Connection
andBuilder
all around.
So these are part of an "environment" that You read from (repeatedly). That's what the Reader
monad is for. The package mtl
contains the monad transformer ReaderT
which adds reader functionality to a base monad, in Your case IO
.
Assuming a simple action, like ..
no_action :: Connection -> Builder -> Int -> IO Int
no_action _ _ i = return (i + 1)
You can put this into a new Monad which is like IO
but with access to both connection and builder by defining a Context
and applying the monad transformer:
data Context = Context { connection :: Connection
, builder :: Builder }
type CBIO b = ReaderT Context IO b
Lifting Your actions into this new (combined) monad deserves a function on its own:
liftCBIO :: (Connection -> Builder -> a -> IO b) -> (a -> CBIO b)
liftCBIO f v = do
context <- ask
liftIO (f (connection context) (builder context) v)
Then You can either always write (liftCBIO no_action) num
or ...
cbio_no_action = liftCBIO no_action
... and cbio_no_action num
.
To actually run Your new monad You'd use runReaderT
.. but this also deserves a better name:
runWithInIO = flip runReaderT
You could also change this to incorporate building the Context
, if You like.
Using the above then looks like that:
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