Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reactive-banana: up-to-date values from fromPoll

I'm writing a music player in Haskell with reactive-banana. One problem I have is fetching up-to-date values with fromPoll. I want to enable the user to optionally select a part of the track while playing. My code looks something like this:

makePlayNetworkDescr :: Player a => AddHandler Command -> a -> NetworkDescription t ()
makePlayNetworkDescr addCmdEvent player = do
    bPosition <- fromPoll (getPosition player)
    eCmds <- fromAddHandler addCmdEvent

    let eSetStart = filterE (isJust) $ bPosition <@ filterE (==SetStart) eCmds
        eSetEnd = filterE (isJust) $ bPosition <@ filterE (==SetEnd) eCmds
        eClearRange = filterE (==ClearRange) eCmds

        bStart = accumB Nothing ((const <$> eSetStart) `union` (const Nothing <$ eClearRange))
        bEnd = accumB Nothing ((const <$> eSetEnd) `union` (const Nothing <$ eClearRange))

Above, getPosition is a partial function, returning Nothing before the playback actually starts. The problem is that once the addCmdEvent fires for the first time, bPosition will still hold a Nothing value. eSetStart/End calculate their values based on this. Only then does bPosition get updated, and this is the value which will be used next time that addCmdEvent fires. And so on, the value will always be "off by one", so to speak.

There is a related SO question, but in that case there exists a "trigger" event which can be used to calculate the new value of the behavior. Is anything like that possible with fromPoll?

like image 597
oggy Avatar asked May 28 '12 16:05

oggy


1 Answers

As of reactive-banana-0.5 and 0.6, the fromPoll function updates the behavior whenever an external event triggers the event network. You can access these updates as an event by using

eUpdate <- changes bSomeBehavior

However, note that behaviors represent continuous time-varying values which do not support a general notion of an "update event". The changes function will try to return a useful approximation, but there are no formal guarantees.

Alternatively, you can change the external event to include the player position as part of the addCmdEvent. In your case, this means to add more data to the SetStart and SetEnd constructors. Then, you can use

eSetStart = filterJust $ matchSetStart <$> eCmds
    where
    matchSetStart (SetStart pos) = Just pos
    matchSetStart _              = Nothing

Both solutions require you to observe the most recent value as an event instead of a behavior. The reason is that behaviors created with stepper will always return the old value at the moment they are updated (they "lag behind by one"), as this is very useful for recursive definitions.

In any case, the underlying issue is that the player position is updated externally long before the addCmdEvent occurs, but the problem is that this is not what the event network sees. Rather, the network thinks that the behavior returned by fromPoll updates simultaneously with the addCmdEvent. In fact, unless you have access to the external event source that is responsible for updating the player position, that's the only thing it can think. (If you do have access, you can use the fromChanges function.)

I realize that this behavior of fromPoll is somewhat unsatisfactory for your common use case. I am undecided whether I should fix it in my library, though: there is a trade-off between fromPoll returning the lastest value and the changes function trying to do its best. If the latest value is returned, then the changes will behave as if it has skipped one update (when the value was updated externally) and triggered a superfluous one (when the network updates the value to match the external one). If you have any opinion on this, please let me know.


Note that combining behaviors with the applicative operator <*> will combine the most recent values just fine.

like image 187
Heinrich Apfelmus Avatar answered Sep 29 '22 19:09

Heinrich Apfelmus