haskellreactive-programmingfrpreactive-banana

Implementation of ifB in reactive-banana


I am reading Conal Elliot's paper "Declarative Event-Oriented Programming" which has its examples written using a Fran library which is now obsolete.

As I am learning FRP I try to implement those examples using reactive-banana. I seem to have no problem with this (but have to think a lot :)). The only thing I do not understand is how to correctly translate Fran's ifB.

It seems to have this type signature:

ifB :: Behavior Bool -> Behavior a -> Behavior a -> Behavior a

But reactive-banana only has switchB for this sort of thing.

Currently I managed to do it by adding an additional "trigger" event which will be used to switch behaviors, like this:

ifB :: MonadMoment m => Event b -> BoolB -> Behavior a -> Behavior a -> m (Behavior a)
ifB trigger condB b1 b2 = switchB b2 (switcherB <@ trigger)
  where switcherB = (\x -> if x then b1 else b2) <$> condB

I am not sure if this is a correct and good solution. Is it performant? Below is a snippet from the paper which uses ifB:

editCPoint :: User -> S.Point2 -> CPoint
editCPoint u p0 = (pos, closeEnough)
  where
    pos, lastRelease :: Point2B
    --    vvv this is the ifB in question!
    pos = ifB grabbing (mouse u) lastRelease
    lastRelease = stepper p0 (release ‘snapshot_‘ pos)

    closeEnough, grabbing :: BoolB
    closeEnough = distance2 pos (mouse u) <* grabDistance
    grabbing = stepper False (grab -=> True .|. release -=> False)

    grab, release :: Event ()
    grab = lbp u ‘whenE‘ closeEnough
    release = lbr u ‘whenE‘ grabbing

grabDistance :: RealB
grabDistance = 2 * pointSize

I managed to get this snippet working with reactive-banana, by using my implementation of ifB and feeding it a cursor move event so it gets updated regularly. I tried to feed it only mouse press/release events as triggers for switching and it didn't work correctly...

Did I do it wrong? Any advice on how to do better?


Solution

  • Behavior has an Applicative instance, which is enough to implement ifB without dynamic switching:

    ifB :: Behavior Bool -> Behavior a -> Behavior a -> Behavior a
    ifB bB bT bF = (\b t f -> if b then t else f) <$> bB <*> bT <*> bF
    

    Or, using bool from Data.Bool:

    ifB :: Behavior Bool -> Behavior a -> Behavior a -> Behavior a
    ifB bB bT bF = bool <$> bF <*> bT <*> bB