HeinrichApfelmus / threepenny-gui

GUI framework that uses the web browser as a display.
https://heinrichapfelmus.github.io/threepenny-gui/
Other
439 stars 77 forks source link

Add 'sinkWhen' function for conditionally updating an 'Attribute'. #56

Closed duplode closed 10 years ago

duplode commented 10 years ago

Inspired by your minimal widget example; more specifically, by how it disconnects view and model while the user edits. I believe the main selling point of sinkWhen is eliminating one reason for using onChange directly. The implementation combines boolean and value into a pair to ensure predictable results if both Behaviors change simultaneously (with separate onChange handlers for each Behavior, if the boolean changed to False at the same time that the value changed then whether the Attribute would be updated would depend on the execution order of the handlers).

P.S.: From reading the Reactive.Threepenny internals, I gather that making a pair in the way I just did is very cheap, as Behaviors synthesized with the Applicative instance are not cached. Is that correct?

HeinrichApfelmus commented 10 years ago

Actually, the only reason why I'm using onChange in this situation is to work around the fact that the browser will reset the caret whenever the value property is set. I don't think the sinkWhen is valuable for anything else, and I would prefer to not include it. (Widgets building on top of the input widget won't have to include the workaround.)

However, it's actually a nice function on the level of behaviors. I would call it

freezeB :: Behavior Bool -> Behavior a -> Behavior a

and it works by "freezing" at the last value of the second argument whenever the first argument becomes True. Here's a picture that hopefully conveys the idea:

freezeb

Much to my surprise, it appears that this function cannot be implemented with the Reactive.Threepenny API! (Not counting the internal functions like onChange). That's very interesting.

P.S.: From reading the Reactive.Threepenny internals, I gather that making a pair in the way I just did is very cheap, as Behaviors synthesized with the Applicative instance are not cached. Is that correct?

Yes, but I wouldn't worry about that even if the answer were no. The FRP in implementation in Reactive.Threepenny is just a prototype with no consideration to performance.

duplode commented 10 years ago

Implementing it on the level of behaviors would be certainly better, but as you say it isn't possible right now (my first attempt to avoid the onChange issues was trying something along those lines). Maybe it is just FRP naïveté, but that didn't feel surprising to me - the problem seems to be that (i) freezeB has to be defined in terms of the value of a Behavior in a previous moment, and (ii) with the current API only an Event can carry information about a specific moment.

P.S.: If I understood the issue well, dynamic event switching would make freezeB possible. I really should try implementing it with reactive-banana as an exercise...

HeinrichApfelmus commented 10 years ago

Using dynamic event switching, it is possible to implement at least a function

freezeEB :: Event Bool -> Behavior a -> Behavior a

Semantically, Behaviors are continuous and have no internal notion of "change event", but the funny thing is that the freezeB still makes sense with these semantics. It boils down to the fact that for a discrete type like Bool, there is a way to make sense of changes, along the lines of

flanks :: Behavior Bool -> Event Bool

In the long run, Threepenny will get dynamic event switching as well, of course. But other things may be more useful in the short term.

duplode commented 10 years ago

With the pull request withdrawn, I took the liberty of closing the issue. (There is the freezeB idea, but I guess you'd rather track that separately, if at all.)