reactive-banana
reactive-banana copied to clipboard
Generalize <@>
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
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))
The term \m e -> observeE $ (\a -> m <*> pure a) <$> e
seems to work.
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 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.