Kogluktualuk: simplifying reactive JavaScript programs

Writing AJAX web applications normally involves a lot of event handlers, which mostly exist to propagate changes to values. Kogluktualuk, or Kog for short, lets you write simple, straightforward code to compute values from other values, and automatically reruns that code when the values change. It’s kind of like a DHTML version of djb/apenwarr redo, a replacement for make.

XXX add example problem from "dependency-directed-dhtml": Okay, so PULL or lazy design. All cells start out unknown. Each cell is associated with a thunk that calculates its value, after which point it is no longer unknown. The thunk runs in a transaction, which notes the input cells that the thunk accesses while running. In general, many of those cells will themselves be unknown, which means their own thunks need to be evaluated in another transaction. There are two options here: you can abort a transaction when it depends on an unknown cell, try to make the cell known, and then if that’s successful, retry the aborted transaction. Or you can keep a transaction stack: pause the outer transaction, start a recursive inner transaction, and if it’s successful, continue the outer transaction. For this case, you have to add an “in-progress” state to detect circular dependencies. Either way, if the transaction is eventually successful, the cell ceases to be unknown; it becomes known, with some particular value; and transactions that tried to read it can continue, if they were paused on the stack, or restart, if they were aborted. The Wikipedia article on reactive programming says: > This change propagation could be achieved in a number of ways, where perhaps the most natural way is an invalidate/lazy-revalidate scheme. Unfortunately “lazy revalidate” seems to have been made up on the spot while writing the article. PULL might seem to have the disadvantage of being completely lazy, and therefore perhaps unsuitable for some kinds of interactive applications (where reliably short response-times are required). But it’s easy to make this drawback disappear: at idle-time, calculate the value of an arbitrary unknown cell, unless there isn’t one. Other possibility: PUSH or eager design. When a cell changes its value, all of the pieces of code that depend on that cell are enqueued to run, and when they run, they can write to other cells. This has several disadvantages over the PULL approach: - In the case of converging dependency chains, you get extra recalculations. - The intermediate recalculations get incorrect results (“glitches”). - Dependency loops become infinite loops. - There’s no option for laziness. In general, in either PUSH or PULL models, there may be some arbitrary time delay between a publisher publishing a change and the subscriber using the new value. If the subscription is deleted during this time, there are several advantages: - Further invalidation notifications, which would be wasted work, will not take place. - When the subscriber gets around to recalculating, if it’s specified by a piece of code, it may turn out that it doesn’t depend on the publisher anymore anyway. Which means that at recalculation time, you need to delete the subscription anyway if it isn’t needed any more. (Consider the rule `holiday_hours if holiday else nonholiday_hours`: depending on the value of `holiday`, either `holiday_hours` or `nonholiday_hours` will be subscribed to, but not both. And `holiday` can change.) - It works as a kind of poor man's weak reference: if the subscriber doesn’t have anything looking at its value, it won’t actually get recalculated, and so it won't get re-added to the subscription list --- which means it can possibly get garbage-collected. It may be a good idea occasionally to make all cells invalid temporarily to help with GC. Note: this could allow really awesome “Whyline” debugging interfaces Conal Elliott’s “Push-Pull Functional Reactive Programming” may be relevant. Also PJE’s “Trellis” in Python, Common Lisp Cells, etc. Notes on Trellis: - it seems to support laziness, but also has some kind of circular-dependency facility I don’t understand; - I think changes to one of an object’s slots are treated as changes to the object; - it has very nice syntactic sugar for defining a bunch of slots at once; - it supposedly uses a lot of memory; - its analogue to the FRP notion of a “signal” (“the leftButtonDown behavior is defined as true from a mouseDown signal with detail leftButton until a mouseUp signal with detail leftButton, false otherwise”) is ‘“resetting” cells’, thus called because they reset to their default value immediately upon being set. - it uses weak references, which is not possible in JS; - it doesn’t seem to use an “unknown” state. Instead there are `@modifier` functions that defer event/constraint/value propagation until they finish, and cells that have not yet been instantiated. This kind of blows away my hypothesis about the PUSH approach being incapable of laziness (I think?). However, it looks like programming with it is a little error-prone because of the outdated values being available. (Incidentally, it looks like Lua’s metatable facility should be sufficient to implement a fairly transparent form of this approach.) From "graph-dataflow": “Cells” work on the principle of subscribing to individual storage locations or “slots”, some of which point to objects with some fixed number of slots themselves. When the value of a slot changes, anything that depended on that slot’s value needs to be recomputed. You can build up functions on these things in the usual way; for example, suppose you want to know whether two lists of integers are equal. You can write: (define (eqlist a b) (if (null? a) (null? b) (if (null? b) #f (and (eq? (car a) (car b)) (eqlist (cdr a) (cdr b)))))) Read normally, this just defines a constant-space procedure for testing the two lists for equality. First it computes whether a and b are themselves null, which depends only on them; then, usually, it extracts their cars and compares them for equality; then, usually, it extracts their cdrs and continues. All in all, in the normal recursive case, it computes the following set of values: a b c: (null? a) #f d: (null? b) #f e: (car a) f: (car b) g: (eq? e f) #t h: (cdr a) i: (cdr b) j: (eqlist h i) Ultimately its output depends on all of these values in the recursive case, and some subset of them in other cases. Some of them are pure functions on the pointer values of their arguments (null? and eq?, normally), while others are memory accessors, depending not only on their argument but also a slot in memory. We can drop the pure functions, so the total set of depended-on values is a, b, e, f, h, i, and j. So you can read this as a procedure not for calculating a single value but for building a dependency network. Flapjax? pycells? cellulose?