"finally" type callbacks?

42 views
Skip to first unread message

Micha Niskin

unread,
Sep 27, 2012, 5:56:42 PM9/27/12
to fla...@googlegroups.com
The general flow of events goes like this:
  1. page load
  2. initial rounds of events fire until page reaches a stable state
  3. wait for user interaction
  4. user triggers an event
  5. triggered event propagates, possibly triggering other events
  6. goto 5 until page reaches a new stable state
  7. goto 3
Steps 4-6 can be called an "event set", perhaps, meaning the set of processing steps that would go on in a spreadsheet in response to a single cell changing, where possibly many cells would be updated, and they would, in turn, cause others to update, and so on.

Now suppose I have a form that is set up to POST to a JSON web service whenever one of its child input elements changes, and suppose that those input elements could have time-varying values that depend on other input elements in the same form. In the model above the form would be submitted multiple times---once for each changed input element. Is there a way to insert a "finally" event in between 6 and 7 somehow, which would be able to submit the form only once per event set? There are quite a few uses for such a thing, as the spreadsheet model sometimes causes duplicated processing that would naturally want to be done after all changes have been propagated. Thanks!

Arjun Guha

unread,
Sep 28, 2012, 11:48:19 AM9/28/12
to fla...@googlegroups.com
Hi Micha,

Good question. This sort of thing pops up a lot. In Flapjax, suppose you have a bunch of event streams, such as the inputs from form elements you describe:

var evt1 = ... clicks ...
var evt2 = ... typing ...
var evt3 = ... copy/paste ...

You can merge these into a single event stream:

var edits = mergeE(evt1, evt2, evt3)

If you edit the form furiously, these events may fire too fast. You can "calm" the event stream to only fire every few seconds:

var calmedEdits = edits.calmE(2000) // 2 sec delay

This drops events that fire in rapid succession, so you don't generate too many POST messages.

Arjun
> --
> Flapjax home page: www.flapjax-lang.org
> Flapjax list: groups.google.com/group/flapjax
> Post: fla...@googlegroups.com
> Unsubscribe: flapjax-u...@googlegroups.com

Micha Niskin

unread,
Sep 28, 2012, 4:20:36 PM9/28/12
to fla...@googlegroups.com
Thanks for the reply!

I am not sure that the issue is the rate of events. It's more of an
abstraction issue.

I think there should be a distinction between initiating events and
secondary events: initiating events being events that are triggered
spontaneously when the application is in a stable state, and secondary
events being events which are triggered by another event during the
spreadsheet processing phase.

It seems to me like there should be some way to have higher-order
processing that can observe the "spreadsheet" as a whole and process
changes from one state to another as if they were atomic (i.e. folding
all intermediate processing into a single event).

Leo Meyerovich

unread,
Sep 28, 2012, 5:39:43 PM9/28/12
to fla...@googlegroups.com
Hiya Micha,

Maybe your concern is related to our early attempts at an 'liftE' combinator.

Intuitive meanings of liftE break down at different clock rates due to ambiguity. Consider somehow adding clicks to seconds, where there are 2 clicks per second:

var bitsClicksE = clicksE.mapE(\x. x % 1) // 0, 1, 0, 1, 0, 1, 0, 1,
var bitsSecondsE = secondsE.mapE(\x. x % 1) //0, 1, 0, 1, 0, 1,
var sumE = liftE(+, bitsClicksE, bitsSecondsE)

---
Hypothetical liftE Semantics A. We might want the sum to be of the most recent values: "sumE = 0, 1, 1, 2, 0, 1, 1, 2, …". We can already write such code:

var sumE = liftB(+, bitsClicksE.startsWith(0), bitsSecondsE.startsWith(0)).changes()

As Arjun noted, calmE is useful in practice for rate limiting.

---
Hypothetical liftE Semantics B. Instead, we might want a one-to-one pairing: "sumE = 0, 2, 0, 2, ….". We can do that with collect:

var stack1B = bitsClicksE.collectE(Array.push, []).startsWith([])
var stack2B = bitsSecondsE.collectE(Array.push, []).startsWith([])
var pairsB = liftB( function (arr1, arr2) { var i = min(arr1.length, arr2.length); return [ arr1[i], arr2[i] ]}, stack1B, stack2B)
var sumE = pairsB.changes().mapE(function (pair) return pair[0] + pair[1])

This code has a space leak, however, as we never trim the already-used parts of the arrays. You can code around that with some local state, essentially adding a real 'pop'. The fix still has an inherent space leak, however, because the token consumption is unbalanced: 2 clicks per second leads to memory consumption of O(|seconds|).

---


It's unclear which interpretation of liftE is desired, and in case B, there's domain knowledge necessary for handling the space leak. We haven't hit a common enough case to pick a particular semantics, but perhaps you have one? I think there are related issues with nested/relative/transactional clock ticks, but those are even murkier.

- Leo


An issue is that events may occur at different clock rates, say seconds and clicks, leading to multiple sane sounding ways of , and basic ways of resolving that ambiguity lead to using local

Micha Niskin

unread,
Sep 28, 2012, 7:05:52 PM9/28/12
to fla...@googlegroups.com
Perhaps I'm not understanding the significance of the clock rate. In
my mental model I clearly distinguish between events that are
initiated spontaneously (i.e. the page is in a stable state and the
user clicks on something, or perhaps a timer fires) from events that
occur during the propagation phase of the spreadsheet-like evaluation.
I'm having a hard time making myself clear so maybe a naive example
would help.

Consider a naive spreadsheet implementation with 3 cells: A1, A2, and
A3. The obvious thing to do is to keep a queue of "dirty" cells, call
it Q. When the spreadsheet loads it would read in values and formulas
for each cell. It would then perhaps push all independent (non
formula) cells onto Q, and run an initial round of processing to reach
an initial consistent state. In this model "consistent state" would
mean that Q is empty (all recalculation of cells that depend on cells
in Q have been done, and Q emptied). Thereafter, when the user edits a
cell, for example, that cell would be added to Q and the evaluator
would run.

Initial state:

A1: 10
A2: A1 + A3
A3: A1 / 2

Values:

A1: 10
A2: 15
A3: 5

User edits A1, changing its value to 20.

Round 1, Q=<A1>, A1=20, A2=15, A3=5.

Q: <>
A1: 20 *since 20 == 20, don't add A1 to Q*
A2: A1+A3 => 25 *since 25 != 15, add A2 to Q*
A3: A1/2 => 10 *since 10 != 5, add A3 to Q*

Round 2, Q=<A2, A3>, A1=20, A2=25, A3=10

Q: <>
A1: 20 *since 20 == 20, don't add A1 to Q*
A2: A1+A3 => 30 *since 30 != 25, add A2 to Q*
A3: A1/2 => 10 *since 10 == 10, don't add A3 to Q*

Round 3, Q=<A2>, A1=20, A2=30, A3=10

Q: <>
A1: 20 *since 20 == 20 don't add A1 to Q*
A2: A1+A3 => 30 *since 30 == 30 don't add A2 to Q*
A3: A1/2 => 10 *since 10 == 10, don't add A3 to Q*

Since Q=<>, processing is now complete. The page is in a stable state,
and is now ready to accept another event from the user.

Now what I am interested in here is an abstraction that represents the
relation between all the events that occurred due to that initial
editing of the A1 cell. I would like to be able to consider a sort of
meta event stream, one that represents a change in state of the page
as a whole, a change that should appear atomic, as if the spreadsheet
was transformed in a single operation with no intermediate rounds of
processing (during which the page is in an inconsistent state). I
would like to be able to work with a stream of global states,
basically.

Leo Meyerovich

unread,
Sep 30, 2012, 9:25:53 PM9/30/12
to fla...@googlegroups.com
Hiya Micha,

Can you describe a desired program that you would like to write? For example, it sounds like you'd like to know if "A3" changed in a reaction, which I show how to do below.

To do so, I exploit a useful property of the event scheduler that is missed by your schedule trace below. In particular, the scheduler respects functional dependencies ('glitch-free'). The dequeue order is topologically sorted according to the dependency graph. For your example, the dequeue order is actually:

A1 (initial mutation)
A3 (only depends on A1)
A2 (depends on both A1 and A3)

This ensures A1, A3, and A2 only fire once per reaction. In particular, only after events they're listening for had their chance to fire. This simplifies reasoning, improves performance for potentially expensive functions, and avoids spurious errors (e.g., spurious divide by zeroes in A1 == -A3 ? 0 : 1/A2 ).

This helps us define a value based on how other values change over time. For example, we can see whether A3 changed in a reaction with any of {A1, A2, A3}:

var a3PeaksE = mergeE(a1B, a2B, a3B).snapshotE(a3B)
var a3ChangedB =
a3PeaksE.collectE(
function (acc, a3) { return {prev: a3, changed: a3 != acc.prev},
{prev: undefined})
.startsWith({changed: false}).liftB(function (pair) { return pair.changed; })


- Leo

Micha Niskin

unread,
Oct 4, 2012, 9:35:24 AM10/4/12
to fla...@googlegroups.com
Hi all,

Thank you for that explanation. I reread the paper and now I think I
understand. I didn't remember the delayB mechanism described there.

Incidentally, does is number of milliseconds specified when calling
delayB significant in terms of breaking cyclic dependencies? I'm
thinking no, that the call to delayB with a delay of 0 ms would still
work for that purpose.

As for the specific application: I've been working on this:
https://github.com/micha/hlisp-starter. The goal of the project is to
provide a single model for abstraction and composition that spans the
entire "front-end" project. My approach is to use ClojureScript, which
is a lisp language, and provide a mechanism for converting the page
markup into lisp expressions that can be evaluated therein. So when
the page loads the HTML body is evaluated as a program and the DOM
body is replaced with the result of this evaluation. Flapjax will
provide the FRP (functional reactive programming) abstraction.

I've been experimenting with flapjax in cljs and I feel good about it.
It seems like there can be really good integration there given the
tools available in cljs. I'd also like to experiment with a Clojure
implementation of Flapjax to see how it can be used for doing server
side FRP in a multithreaded environment.

Shriram Krishnamurthi

unread,
Oct 4, 2012, 5:06:04 PM10/4/12
to fla...@googlegroups.com, Jay McCarthy
> I've been experimenting with flapjax in cljs and I feel good about it.
> It seems like there can be really good integration there given the
> tools available in cljs. I'd also like to experiment with a Clojure
> implementation of Flapjax to see how it can be used for doing server
> side FRP in a multithreaded environment.

Have you seen FrTime, which is built into the DrRacket programming environment?

Also, I believe Jay McCarthy (CCed) has his own server-side FRP framework.

Shriram
Reply all
Reply to author
Forward
0 new messages