Search code examples
haskellreactive-banana

How does Reactive Banana's mapAccum function work?


I have looked at a number answers to questions here on Stack Overflow trying to find a solution to my problem in using the Reactive Banana library. All the answers use some magic using 'mapAccum' that I can't quite understand. Looking at the API documentation all I find is "Efficient combination of accumE and accumB." which is not very helpful.

It seems that this function can be used to compare the values of a Behavior at the time of two consecutive events which is what I'd like to do. But I not clear how to make that work.

How exactly does mapAccum work?


Solution

  • Notice that

    mapAccum :: acc -> Event t (acc -> (x, acc)) -> (Event t x, Behavior t acc)
    

    so it takes an initial value :: acc to accumulate on, and an Event which produces a function that updates that accumulated value whilst producing an output value ::x. (Typically you'd make such an event by partially applying some function via <$>.) As a result you get a new Event that fires your x values whenever they turn up and a Behaviour containing your current accumulated value.

    Use mapAccum if you have an event and you want to make a related behaviour and event.

    For example in your problem domain from your other question, suppose you have an event eTime :: Event t Int that fired erratically and you wanted to calculate eDeltaTime :: Event t Int for the differences and bTimeAgain :: Behaviour t Int for the currently used time:

    type Time = Int
    type DeltaTime = Time 
    
    getDelta :: Time -> Time -> (DeltaTime,Time)
    getDelta new old = (new-old,new)
    

    I could have written that getDelta new = \old -> (new-old,new) to make the next step clearer:

    deltaMaker :: Event t (Time -> (DeltaTime,Time))
    deltaMaker = getDelta <$> eTime
    
    (eDeltaT,bTimeAgain) = mapAccum 0 $ deltaMaker
    

    In this case, bTimeAgain would be a behaviour with the same value as the events in eTime. This happens because my getDelta function passes new straight through unchanged from eTime to the acc value. (If I wanted bTimeAgain on its own, I would have used stepper :: a -> Event t a -> Behaviour t a.) If I don't need bTimeAgain, I could just write (eDeltaT,_) = mapAccum 0 $ deltaMaker.