Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pure function for Behavior updates #151

Open
nickkuk opened this issue Jul 14, 2017 · 6 comments
Open

Pure function for Behavior updates #151

nickkuk opened this issue Jul 14, 2017 · 6 comments

Comments

@nickkuk
Copy link

nickkuk commented Jul 14, 2017

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.

@HeinrichApfelmus
Copy link
Owner

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
Copy link
Author

nickkuk commented Jul 25, 2017

@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 😃 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
Copy link
Collaborator

mitchellwrosen commented Aug 28, 2017

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
Copy link

joeyh commented Aug 16, 2020

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
Copy link
Collaborator

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
Copy link
Owner

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants