【发布时间】:2019-04-16 10:50:25
【问题描述】:
我正在尝试使用HDBC 和Haskell.GI 实现一个小型桌面应用程序。我使用 glade 构建我的窗口和对话框,并使用 GtkBuilder 加载它们。在实现了几个场景之后,我最终使用了相同的模式,在 do 块中组成“动作”,其签名为:
Connection -> Builder -> a -> IO b
这些“动作”在IO monad 的上下文中构成,主要问题是我必须到处传递我的Connection 和Builder。我预见的另一个问题是,如果我想向我的应用程序添加另一个外部依赖项(例如,访问图像扫描仪),我将不得不更改所有“动作”的签名,更重要的是,它们的数量。
我能做什么:我可以定义一个类型同义词:
type Action a b = Connection -> Builder -> a -> IO b
我还可以创建一个命名元组来消除元组问题:
data Context =
Context {
conn :: Connection,
builder :: Builder}
但这仍然不能解决这样一个事实,即每次我想访问数据库时,我都必须调用(conn ctx) 或在每个操作中使用let 绑定。
我觉得最理想的做法是制作我自己的 monad,我可以在其中组合我的行为,并且我不会明确谈论我的 Connection 或 Builder 值。
知道IO 已经是一个monad,我该如何定义这样的monad?
顺便说一句,它与State monad 有什么关系吗?
【问题讨论】:
-
听起来你可能想看看 Reader monad。 (它确实类似于 State monad,但 state 是只读的。)
-
@RobinZigmond:好的。你的意思是:如果我在问题中定义
Context类型,并定义type Action = ReaderT Context IO,我可以做(reader conn)、(reader builder)和liftIO $ do ...? -
不完全 -
(ReaderT Context IO) b,模newtype包装,相当于Context -> IO b- 所以它不包括你的a参数。您当然可以重新排列参数的顺序并使用函数a -> ReaderT Context IO b -
所以
(new)type Action a b = a -> ReaderT Context IO b将是一个适当的单子可组合函数类型。 (单子是ReaderT Context IO)我想我现在明白了。 -
使用自己的 monad,您可以定义自己的助手,例如
getConnection :: Action a Connection。您可能还想定义一个MonadIO (Action a)实例,以便您可以在自定义 monad 中运行liftIO (putStrLn "hello")之类的东西。