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
我希望将此要求编码为一种类型,这样 action1
、action2
、action3
只能在之后未抓取按钮的情况下使用。也就是说,尽管 action1
、action2
确实是 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
这样人们就不会不小心将 action1
、action2
、action3
用作 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
动作。所以明确一点:这不是面向安全的安全,只是防止人们不小心忘记清理。
我有一系列 X()
操作,在此期间可能会抓住某些按钮(之后不会释放)。为了防止按钮最终被抓取,因此我必须在最后取消抓取每个按钮,例如:
action1 >> action2 >> action3 >> ungrabAllButtons
我希望将此要求编码为一种类型,这样 action1
、action2
、action3
只能在之后未抓取按钮的情况下使用。也就是说,尽管 action1
、action2
确实是 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
这样人们就不会不小心将 action1
、action2
、action3
用作 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
动作。所以明确一点:这不是面向安全的安全,只是防止人们不小心忘记清理。