Reactive-banana: up-to-date values from fromPoll
Asked Answered
H

1

6

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?

Hepler answered 28/5, 2012 at 16:44 Comment(2)
When you're finished, you should put the result on Hackage. Looking forward to playing my music from Haskell!You
I do plan to put it on Hackage once (hah) I'm finished. However it's a "scratch my own itch" kind of music player to aid with transcribing music, so I'm not sure how interesting would it be to other people.Hepler
H
2

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.

Heptastich answered 29/5, 2012 at 12:16 Comment(1)
Thanks for the reply. In the end I went the route of polling the position data externally and including it in the Event. Thinking about it some more, I can't really decide whether there are any conceptual benefits from getting the fresh value from fromPoll as compared to getting it from an external source, nor the other way round. On the other hand, I don't quite get the problem of fromPoll "blocking" until the fresh value is read, and only then firing the event which has actually triggered this evaluation (simultaneously with the changes event), but you know your library much better than meHepler

© 2022 - 2024 — McMap. All rights reserved.