问题
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