问题
I have a sequence of X()
actions during which certain buttons might be grabbed (and not released afterwards). In order to prevent buttons from ending up grabbed, I therefore have to ungrab every button at the end, for example:
action1 >> action2 >> action3 >> ungrabAllButtons
I wish to encode this requirement as a type, so that action1
, action2
, action3
can only be used if the buttons are ungrabbed afterwards. That is, even though action1
, action2
, are really X()
actions, I would like them not to be usable as such unless they are wrapped in something like the following (borrowing Python's with
keyword):
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
This is so that people do not accidentally use action1
, action2
, action3
as X()
actions, while forgetting to ungrab the buttons afterwards.
Is this possible with Haskell's type system? Thanks beforehand.
回答1:
What you will want to do is make a newtype wrapper for X
, using GeneralizedNewtypeDeriving
to get a free Monad
instance:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype XNeedsCleanup a = FromX { toX :: X a }
deriving (Functor, Applicative, Monad)
Because XNeedsCleanup
is a monad, you can bind multiple XNeedsCleanup
together, as in your action1 >> action2 >> action3
example; this will bind all of their wrapped X
actions together inside the FromX
wrapper. But you won't be able to bind the resulting action with an X
action; that's where your withGrabbedButtons
comes in:
withGrabbedButtons :: XNeedsCleanup () -> X ()
withGrabbedButtons action = toX action >> ungrabAllButtons
If you don't export the toX
unwrapper, your clients won't be able to use an XNeedsCleanup
value without going through withGrabbedButtons
. But if they have the ability to use arbitrary X
actions, then presumably they can import whatever you use to define your various actions and could reimplement them as "raw" X
actions. So just to be explicit: this isn't security-oriented safety, just preventing people from accidentally forgetting the cleanup.
回答2:
You might want to create your own version of bracket. Bracket works in the IO
monad, but based on a quick peek at the source code, I suspect you could make your own version that runs in the X
monad. Bracket will ensure that any finalisation (e.g. ungrabbing all buttons) occurs, even if an exception is raised.
来源:https://stackoverflow.com/questions/30106827/haskell-xmonad-what-is-the-natural-type-for-expressing-that-something-must-be-d