reactive-banana icon indicating copy to clipboard operation
reactive-banana copied to clipboard

Generalize <@>

Open mitchellwrosen opened this issue 6 years ago • 4 comments

When doing higher-order FRP, the Moment concept comes up often. It's similar to Behavior in that a Moment a represents an a that has a value at every point in time. (Aside: more documentation about this concept would be helpful; in the "model" implementation, Moment and Behavior are isomorphic, which is confusing).

For this reason I wonder if the <@> could/should be generalized to address both of these uses:

(<@>) :: Behavior (a -> b) -> Event a -> Event b
(<@>) :: Moment (a -> b) -> Event a -> Event b

mitchellwrosen avatar Jun 01 '18 19:06 mitchellwrosen

As a motivating example, here's a simple first-order rendering of some widgets:

-- render event and rendering function
eRender :: Event ()
render :: [Widget] -> IO ()
bScene :: Behavior [Widget]

reactimate (render <$> bScene <@ eRender)

Now say the widgets themselves are dynamic, so we move to higher-order combinators:

bScene :: Behavior [Behavior Widget]

reactimate (render <$> observeE ((valueB . sequenceA) <$> bScene <@ eRender))

This is the version I'd like to clean up, somehow, because I find it very difficult to read and understand.

One observation is that everything is much simpler in the Moment monad with do-notation, and there is not much need for <@> on Behavior, which can be written using valueB instead.

Instead of the above, I could drill down into the time-varying [Widget] to render like so:

mScene :: Moment [Widget]
mScene = do
  bWidgets <- valueB bScene
  valueB (sequenceA bWidgets)

The very last step of "sampling a time-varying thing when an event occurs" is where I could benefit from <@ on Moment, as:

reactimate (render <$> mScene <@ eRender)

But instead, I have to write:

reactimate (render <$> observeE (mScene <$ eRender))

mitchellwrosen avatar Jun 06 '18 15:06 mitchellwrosen

The term \m e -> observeE $ (\a -> m <*> pure a) <$> e seems to work.

ChristopherKing42 avatar Sep 10 '19 22:09 ChristopherKing42

Personally I think using observeE is the way to go.

This is the version I'd like to clean up, somehow, because I find it very difficult to read and understand.

reactimate (render <$> observeE ((valueB . sequenceA) <$> bScene <@ eRender))

I would write this as:

reactimate $ observeE $ eRender $> do
  bWidgets <- valueB $ sequenceA bScene
  widgets <- valueB bWidgets
  return $ render widgets

@mitchellwrosen I'm generally pretty averse to type classes just for overloading. Is this something you strongly desire, or do you think using observeE (or generally working less point-free, like with your mScene snippet) is acceptable?

ocharles avatar Sep 16 '21 16:09 ocharles

@ocharles Good question... I too am generally averse to ad-hoc overloading, and it would also be nice not to make a beginner's first-order FRP experience more complicated in service of a power user who can wrangle higher-order FRP.

(Note: that ship has begun to sail anyway with the inclusion of Moment in the type of accumB)

That said, I feel the semantics and similarity between Moment and Behavior is captured very well by the generalization of <@>.

Moment and Behavior are in fact both conceptually identical - a value that varies over time. Such a thing can be sampled whenever an event fires at a particular moment in time - that's <@>!

The difference between Moment and Behavior only leaks through in combinators like changes and switchB, where it's relevant that Moment cannot efficiently communicate when its value changes. But that's definitely an irrelevant distinction if all you want to do is sample the time-varying value.

mitchellwrosen avatar Sep 16 '21 17:09 mitchellwrosen