reactive-banana
reactive-banana copied to clipboard
Pure function for Behavior updates
Can we have pure function, similar to changes with type like Behavior a -> Moment (Event (Future a)) (or Behavior a -> Moment (Event a), or even Behavior a -> Event a)? E.g. Sodium library has such a function: updates. There were some related questions on stackoverflow: 1, 2, 3. At now proposed solutions to these kind of problems are new types:
- type Reactive a = (a,Event a)
- type Dynamic a = (Behavior a, Event a)
- type Discrete a = (a, Event a, Behavior a)
But these types seem to be redundant and not as modular as simple Behavior type.
Sorry for the late reply.
Semantically, Behavior a
can be represented as a function Time → a
. These semantics do not support a changes
function like you suggest; there is no way to tell "how often" the Behavior has changed. (For pragmatic reasons, the changes
function exists, but it does not have a pure type.) So, at present, I do not intend to add a pure version of the changes
function.
Of course, one can debate whether these semantics are a good idea. I do, however, think that they are: They eliminate a source of bugs. A Behavior can have a "change" but still have the same value before and after. The current semantics make it impossible for pure code to distinguish between the two, and I think that's a good thing. This is also explained here (point 1).
@HeinrichApfelmus Maybe one really don't need such a function..
Let me describe my problem from a scratch, as it is related to your article. I work in computer graphics company - not games, but 3d editor like 3dsMax with photorealistic rendering. Very often we have to draw GUI widgets ourselves in pure OpenGL/DirectX, because we need them to work in 3d, they must be fast, they must look the same on different platforms.
So we have only inputs from window (e.g. resolution and mouse clicks) as reactive-banana AddHandlers and reactimate (draw <$> ... <@ eRedraw) with draw :: ... -> IO () for output. All logic for the widget should be pure and live in its encapsulated events and behaviors. My current solution for such an encapsulation is this:
data GlobalEvents = GlobalEvents {
eRedraw :: Event (),
bResolution :: Behavior (Int, Int),
eDragBegin :: Event (Int, Int),
eDrag :: Event (Int, Int),
eDragEnd :: Event ()
}
data Widget = Widget {
bWidgetVisible :: Behavior Bool,
eWidgetDragBegin :: Event (),
eWidgetDrag :: Event (Int, Int),
eWidgetDragEnd :: Event (),
bWidgetDragged :: Behavior Bool
}
createWidget :: MonadMoment m => GlobalEvents -> Behavior Rectangle -> Bool -> Event Bool -> m Widget
createWidget globEvents bRect initialVisible eVisibilityChange = do
bWidgetVisible <- stepper initialVisible eVisibilityChange
let testRect rect p = if inRect p rect then Just () else Nothing
let eWidgetDragBegin = filterJust $ testRect <$> bRect <@> whenE bWidgetVisible (eDragBegin globEvents)
let eHide = () <$ filterE not eVisibilityChange
let eDragEndV = unionWith const (eDragEnd globEvents) eHide
bWidgetDragged <- stepper False (unionWith const (True <$ eWidgetDragBegin) (False <$ eDragEndV))
let eWidgetDrag = whenE bWidgetDragged (eDrag globEvents)
let eWidgetDragEnd = whenE bWidgetDragged eDragEndV
return Widget {..}
Here we can't pass bWidgetVisible to function, because we need to know eHide moment to turn off bWidgetDragged. Maybe it is fully wrong solution with FRP, but I didn't see any pure FRP widgets with encapsulation: in reactive-banana-wx you have properties and have to do reactimate to change them; in threepenny-gui I see the same picture: you have attributes and changes them in reactimate-like functions - all widget's inner state is stored in external library in IO world.
Based on your experience, can you suggest the right approach for such pure-FRP-widget constructor function(s)? (Let's do pure FRP text edit :smiley: I will help with OpenGL shaders and font textures)
P.S. I also tried to do fully dynamic widget (oppositely to having visibility), but switchE startled me during my first attempt: #152.
I think something like Dynamic is useful here, which is just a Behavior
paired with the Event
that it is stepped by (note this is not the same thing as Tidings
from threepenny-gui
). Then you can have your cake and eat it too!
-- constructor kept abstract
data Dynamic a
= Dynamic (Behavior a) (Event a)
deriving Functor
current :: Dynamic a -> Behavior a
current (Dynamic x _) = x
updates :: Dynamic a -> Event a
updates (Dynamic _ x) = x
accumD :: MonadMoment m => a -> Event (a -> a) -> m (Dynamic a)
accumD x fs = do
(e, b) <- mapAccum x ((\f y -> dup (f y)) <$> fs)
pure (Dynamic b e)
where
dup z = (z, z)
Relatedly, I have a need of a function a -> Behavior (Maybe a) -> Behavior a
.
This is not currently easy to build, because you can't accumulate over a Behavior -- for good reasons as @HeinrichApfelmus explains. But skipping over all Nothing values and taking only the Justs does not allow distinguishing between two Behaviors that are identical functions no matter how "often" they might occur.
Currently the user has to find the plainChanges example in Frameworks and understand it well enough (or not) to use it safely, in order to make something like that.
It seems that an accumulator, that only runs the function when the Behavior changed to a different value, would be safe to include.