Как вернуться к предыдущему моменту

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

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, чтобы знать, нажата ли кнопка в данный момент или нет.

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


person Ryoichiro Oka    schedule 27.07.2015    source источник


Ответы (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 может оказаться полезной. Помните о предостережениях, связанных с этим, упомянутых в документации.
person duplode    schedule 27.07.2015
comment
Спасибо @duplode за ваш ответ, где вы запускаете эту демонстрацию? Я запустил его в CMD, но он распечатал True только тогда, когда я набрал y, потому что getLine ждет моего ввода, и только y можно ввести в buttonState из-за фильтра (== "y") - person Ryoichiro Oka; 28.07.2015
comment
@RyoichiroOka Пока вы получаете False, когда вводите что-то, кроме y, это работает так, как задумано. Идея состоит в том, что (== "y") <$> getLine является макетом вашего readButton действия ввода-вывода. Макет возвращает True, когда вы вводите y, и False в противном случае. - person duplode; 28.07.2015
comment
Я получил идею. Спасибо! Также мне интересно, как bButtonState <@ bButtonDriver работает, чтобы оглянуться назад в предыдущее состояние. Означает ли это, что я могу заглянуть так далеко назад, как захочу? - person Ryoichiro Oka; 28.07.2015
comment
@RyoichiroOka На самом деле это не взгляд назад. Это работает, потому что stepper выполняет обновление (условно) слегка после инициирующего события, чтобы сработали варианты использования, подобные этому. Поскольку eButtonDriver и ePreviousState выполняются одновременно, ePreviousState наблюдает за значением непосредственно перед обновлением. Возможно, было бы понятнее, если бы я назвал его вместо ePreviousState eCurrentState (в отличие от нового состояния, привнесенного eButtonDriver). - person duplode; 28.07.2015
comment
Я посмотрю документ. Большое спасибо :) Я буду публиковать еще несколько вопросов о реактивном банане в течение нескольких дней, и я буду признателен, если вы останетесь. - person Ryoichiro Oka; 28.07.2015
comment
Я пытаюсь обобщить вещи. Как вы думаете, правильно ли эта функция связывает предыдущее и текущее значения данного потока событий и заданное начальное значение потока? \default event -> (,) <$> stepper default event <@> event @duplode - person Ryoichiro Oka; 28.07.2015