Как оглянуться назад на предыдущий момент


Я читаю состояние кнопки (независимо от того, нажата она или нет) каждый момент:

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
    ...

Все состояния чтения хранятся в eButtonState в описании сети desc.

Кнопка считается вновь нажатой, когда состояние текущего момента 1 совпадает с состоянием предыдущего момента 0. Итак, если бы последовательность событий была списком, функция была бы записана следующим образом:
f :: [Bool] -> Bool
f (True:False:_) = True
f _              = False

Я хочу применить эту функцию к eButtonState, чтобы узнать, является ли кнопка новой нажал или нет в данный момент.

Возможно ли это вообще? Как бы вы это сделали? Я был бы признателен, если бы существовала лучшая или более распространенная идея или метод для достижения этой цели.

1 4

1 ответ:

Вот один из способов (это запускаемая демонстрация):

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

Примечания:

  • события преходящи, поведение постоянно - хорошее общее правило, чтобы решить, нужно ли вам поведение или поток событий. В этом случае вам нужно посмотреть, каким было состояние кнопки до обновления, чтобы решить, была ли она обновлена заново. Поэтому естественно представить состояние кнопки с поведением (bButtonState), которое обновляется событием, запущенным извне (eButtonDriver).
  • подробнее о том, что делают комбинаторы, см. Reactive.Banana.Combinators.
  • для точного определения времени событий и обновления поведения в реактивном банане см.этот вопрос .
  • в зависимости от того, что вы пытаетесь сделать,changes функция может быть полезной. Имейте в виду предостережения, связанные с ним, упомянутые в документации.