Haskell/XMonad: 表示在一系列动作之后必须做某事的自然类型是什么?

Haskell/XMonad: What is the natural type for expressing that something must be done after a sequence of actions?

我有一系列 X() 操作,在此期间可能会抓住某些按钮(之后不会释放)。为了防止按钮最终被抓取,因此我必须在最后取消抓取每个按钮,例如:

action1 >> action2 >> action3 >> ungrabAllButtons

我希望将此要求编码为一种类型,这样 action1action2action3 只能在之后未抓取按钮的情况下使用。也就是说,尽管 action1action2 确实是 X() 操作,但我希望它们不能这样使用,除非它们被包裹在类似下面的东西中(借用 Python的with关键字):

withGrabbedButtons :: ??? -> X()
withGrabbedButtons action =
  action >> ungrabAllButtons  


-- correct ; complete_action does not leave the mouse grabbed
complete_action :: X()
complete_action = withGrabbedButtons (action1 >> action2 >> action3)

-- type error!
erroneous_single_action :: X()
erroneous_single_action = action1

-- type error!
erroneous_action :: X()
erroneous_action = action1 >> action2 >> action3

这样人们就不会不小心将 action1action2action3 用作 X() 操作,同时忘记之后取消抓取按钮。

Haskell 的类型系统可以吗?预先感谢。

您可能想要创建自己的 bracket 版本。 Bracket 在 IO monad 中工作,但基于对源代码的快速浏览,我怀疑您可以制作在 X monad 中运行的自己的版本。 Bracket 将确保任何最终确定(例如取消抓取所有按钮)发生,即使引发异常也是如此。

您要做的是为 X 制作一个新类型包装器,使用 GeneralizedNewtypeDeriving 获得一个免费的 Monad 实例:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

newtype XNeedsCleanup a = FromX { toX :: X a }
  deriving (Functor, Applicative, Monad)

因为 XNeedsCleanup 是一个单子,您可以将多个 XNeedsCleanup 绑定在一起,如您的 action1 >> action2 >> action3 示例;这会将所有包装的 X 动作绑定在 FromX 包装器中。但是您将无法将结果操作与 X 操作绑定;那就是你的 withGrabbedButtons 进来的地方:

withGrabbedButtons :: XNeedsCleanup () -> X ()
withGrabbedButtons action = toX action >> ungrabAllButtons

如果您不导出 toX 解包器,您的客户将无法在不经过 withGrabbedButtons 的情况下使用 XNeedsCleanup 值。但是,如果他们有能力使用任意 X 动作,那么大概他们可以导入您用来定义各种动作的任何内容,并可以将它们重新实现为 "raw" X 动作。所以明确一点:这不是面向安全的安全,只是防止人们不小心忘记清理。