HeinrichApfelmus / reactive-banana

Library for functional reactive programming in Haskell.
https://wiki.haskell.org/Reactive-banana
522 stars 71 forks source link

Pure function for Behavior updates #151

Open nickkuk opened 7 years ago

nickkuk commented 7 years ago

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:

But these types seem to be redundant and not as modular as simple Behavior type.

HeinrichApfelmus commented 7 years ago

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).

nickkuk commented 7 years ago

@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.

mitchellwrosen commented 7 years ago

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)
joeyh commented 4 years ago

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.

mitchellwrosen commented 1 year ago

Relatedly, I have a need of a function a -> Behavior (Maybe a) -> Behavior a.

What would this function do? Presumably it's not f x b = fromMaybe x <$> b

HeinrichApfelmus commented 1 year ago

I think that a function

detectEdges :: Behavior (Maybe a) → Event (Future a)

is possible within the semantics of continuous time — we can't tell when a Behavior "changes" in general, but we can detect when a Nothing changes to a Just.