3

I am reading a button's state (whether being pressed or not) every moment:

readButton :: IO Boolean
readButton = ...

main = do
    (add, fire) <- newAddHandler
    network <- compile (desc add)
    actuate network
    forever $ do
        buttonState <- readButton
        fire buttonState

desc addButtonEvent = do
    eButtonState <- fromAddHandler addButtonEvent
    ...

All the read states are stored into eButtonState in the network description desc.

The button is considered to be newly pressed when the current moment's state is 1 with the previous moment's being 0. So, if the event sequence was a list, the function would be written like this:

f :: [Bool] -> Bool
f (True:False:_) = True
f _              = False

I want to apply this function to eButtonState so I would know whether the button is newly pressed or not in the moment.

Is it ever possible? How would you do it? I would appreciate if there is a better or more common idea or method to achieve this goal.

duplode
  • 33,731
  • 7
  • 79
  • 150
Ryoichiro Oka
  • 1,949
  • 2
  • 13
  • 20

1 Answers1

3

Here is one way (this is a runnable demo):

import Reactive.Banana
import Reactive.Banana.Frameworks
import Control.Monad
import Control.Applicative -- Needed if you aren't on GHC 7.10.

desc addDriver = do
    -- Refreshes the button state. Presumably fired by external IO.
    eButtonDriver <- fromAddHandler addDriver
    let -- Canonical repersentation of the button state.
        bButtonState = stepper False eButtonDriver
        -- Observes the button just before changing its state.
        ePreviousState = bButtonState <@ eButtonDriver
        -- Performs the test your f function would do.
        newlyPressed :: Bool -> Bool -> Bool
        newlyPressed previous current = not previous && current
        -- Applies the test. This works because eButtonDriver and
        -- ePreviousState are fired simultaneously.
        eNewlyPressed = unionWith newlyPressed
            ePreviousState eButtonDriver
        -- The same but more compactly, without needing ePreviousState.
        {-
        eNewlyPressed = newlyPressed <$> bButtonState <@> eButtonDriver
        -}
    reactimate (print <$> eNewlyPressed)

main = do
    (addDriver, fireDriver) <- newAddHandler
    network <- compile (desc addDriver)
    actuate network
    -- Demo: enter y to turn the button on, and any other string to
    -- turn it off.
    forever $ do
        buttonState <- (== "y") <$> getLine
        fireDriver buttonState

Notes:

  • Events are transient, behaviors are permanent is a good general rule to decide whether you need a behavior or an event stream. In this case, you need to look at what the button state was before the update in order to decide whether it was newly updated. The natural thing to do, then, is to represent the button state with a behavior (bButtonState), which is updated by an event fired externally (eButtonDriver).
  • For details about what the combinators are doing, see Reactive.Banana.Combinators.
  • For the fine print on the timing of events and behavior updates in reactive-banana, see this question.
  • Depending on what you are trying to do, the changes function might be useful. Be aware of the caveats related to it mentioned by the documentation.
Community
  • 1
  • 1
duplode
  • 33,731
  • 7
  • 79
  • 150
  • Thank you @duplode for your answer, where do you run this demo? I ran it on CMD but it printed out `True` only when I typed in a `y`, because `getLine` waits for my input and only a `y` can be input to `buttonState` because of the `(== "y")` filter – Ryoichiro Oka Jul 27 '15 at 23:43
  • 1
    @RyoichiroOka As long as you are getting `False` when you enter something other than "y", it is working as intended. The idea is that `(== "y") <$> getLine` is a mock-up of your `readButton` IO action. The mock-up returns `True` when you type a "y" and `False` otherwise. – duplode Jul 27 '15 at 23:53
  • I got the idea. Thank you! Also I wonder how come `bButtonState <@ bButtonDriver` works to look back the previous state. Does this mean I can look as any farther back as I want? – Ryoichiro Oka Jul 27 '15 at 23:53
  • 1
    @RyoichiroOka It is not really a look back. It works because `stepper` performs the update (notionally) ["slightly after"](http://hackage.haskell.org/package/reactive-banana-0.8.1.2/docs/Reactive-Banana-Combinators.html#v:stepper) the triggering event, so that use cases such as this one work. As `eButtonDriver` and `ePreviousState` are simultaneous, `ePreviousState` observes the value just before the update. Perhaps it would have been clearer if I called it, instead of `ePreviousState`, `eCurrentState` (as opposed to the new state brought by `eButtonDriver`). – duplode Jul 28 '15 at 00:06
  • I will look up the document. Thank you a lot :) I will be posting some more questions about reactive-banana for some days and I will appreciate if you could stick around. – Ryoichiro Oka Jul 28 '15 at 00:10
  • 1
    I'm trying to summarize things. Do you think this function correctly pairs up the previous and current values of a given event stream and a given initial value of the stream? `\default event -> (,) <$> stepper default event <@> event` @duplode – Ryoichiro Oka Jul 28 '15 at 08:34