Search code examples
haskellfrpthreepenny-gui

Accumulating events with a resettable initial value


I need a function like accumB, but I would like the initial value to be a Behavior instead of a constant. When the initial value changes, the accumulator should 'reset' and start accumulating from that value. Concretely, for the following code and sequence of events:

accumB' :: Behavior a -> Event (a -> a) -> IO (Behavior a)
accumB' (stepper 0 ev) ((+) <$> ev')

ev' -> 1
ev' -> 1
ev -> 5
ev' -> 1

accumB' should return a step function of values 0, 1, 2, 5, 6.

Is this possible with the combinators currently available in Threepenny, or does it require support for dynamic switching? (I think the answer is 'no', so I'm currently trying to implement my own accumB' in terms of IORefs...) And if not, is accumB' as described above semantically well-defined, or should I be using an Event a for my time-varying initial value instead?


Solution

  • As J. Abrahamson pointed out in the comments, a Behavior varies continuously in time, so the discrete events that mark changes to a Behavior aren't well defined. Here are some alternatives.

    Change behavior by event

    If you are using the reactive package and are looking at changing Behaviors based on Events, then switcher has the correct type:

    switcher :: Behavior a -> Event (Behavior a) -> Behavior a
    

    Accumulate events with reset

    As you mention in the comments, if you want to reset an accumulator on an event to x, simply put a const x event in the stream. This example uses the reactive package.

    accumBReset :: a -> Event a -> Event (a -> a) -> Behavior a
    accumBReset initial resets changes =
        accumB initial allChanges
            where
                allChanges = (fmap const resets) `mappend` changes
    

    Reactive

    In Push-pull Functional Reactive Programming, Conal Elliott describes the Reactive type, which is analogous to a Behavior that only changes at discrete moments in time.

    data Reactive a = a `Stepper` Event a
    newtype Event a = Ev (Future (Reactive a))
    

    Reactive can be converted into a stream of events marking its changes by deconstructing it and taking the right side of the constructor which is the event that next changes its value

    changes :: Reactive a -> Event a
    changes (_ `Stepper` nextChange) = nextChange
    

    Alternatively, we could get all the values of the Reactive, including what it is now and all of its future changes

    values :: Reactive a -> Event a
    values = Ev . pure
    

    Reactive values are in FRP.Reactive.Reactive in the reactive package.

    Get changes from a Behavior at a lower level

    In some FRP libraries, you can do more at a lower level. In reactive-banana you can observe changes to a Behavior when making your own Framework. Here's the type for Reactive.Banana.Framework's changes

    changes :: Frameworks t => Behavior t a -> Moment t (Event t (Future a))
    

    The documentation warns that this isn't really meaningful:

    Output, observe when a Behavior changes.

    Strictly speaking, a Behavior denotes a value that varies continuously in time, so there is no well-defined event which indicates when the behavior changes.

    Still, for reasons of efficiency, the library provides a way to observe changes when the behavior is a step function, for instance as created by stepper. There are no formal guarantees, but the idea is that

    changes (stepper x e) = return (calm e)

    Note: The values of the event will not become available until event processing is complete. It can be used only in the context of reactimate'.