1

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.

spacingissue
  • 497
  • 2
  • 12
  • 1
    You'd have to define a new monad and make `actionX` work on that monad and have `withGrabbedButtons`/`ungrabAllButtons` convert the value from one monad to the other... but `actionX` will *not* be `X()` actions. – Bakuriu May 07 '15 at 16:50
  • Thanks; is there a good example of this? – spacingissue May 08 '15 at 05:10
  • The stm monad might be a good example. – monocell May 08 '15 at 15:34
  • You may also want to read "Kleisli Arrows of Outrageous Fortune" for a more flexible approach to resource management. – dfeuer May 08 '15 at 18:16

2 Answers2

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.

Ben
  • 68,572
  • 20
  • 126
  • 174
0

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.

mhwombat
  • 8,026
  • 28
  • 53
  • That wasn't quite what I was asking. I can write the `withGrabbedButtons` function, though bracket might be better. What I was asking was how to make it a type error to use the action outside of `withGrabbedButtons`. – spacingissue May 08 '15 at 05:03