Core.async UI first questions

As discussed in my cljs architecture post post, frontend architecture decomposes as two orthogonal concerns:

Angular takes the DOM seriously by associating view models (“scopes”) with DOM subtrees automatically. This is by far the best approach I’ve seen, especially when it comes to collaborating with designers—they can manipulate plain HTML markup as they see fit, without having to worry about tweaking tags, classes, or IDs that the application might depend on. However, since core.async offers nothing for DOM manipulation, I won’t discuss it further.

As for state propagation, Angular uses a dirty checking loop that invokes all watches on all scopes until they reach a fixed point. (See the “dirty checking” section of the cljs architecture post for more details.)

Pros of Angular’s approach:

Cons of Angular’s approach:

Core.async is pretty fresh when it comes to UI, and there aren’t a ton of established patterns. One of the most interesting (pointed out by David Nolen) is that core.async enables local event loops, in contrast to the single global event loop of vanilla JS. People are writing

(go (loop [my-thing (<! a-channel)
           with ... other ... state ...]
       ...
       (recur (<! a-channel) with other state)))

throughout their applications; one go block loop per concern. (See Bruce Hauman’s core.async dots game and David Nolen’s autocompleter.)

However, the community seems to agree that go blocks should be stateless and I haven’t seen any patterns emerging for sharing explicit state between go blocks.

Furthermore, even if it’s conceptually nice to have separate local event loops, when it comes to browser performance, DOM manipulation (i.e., painting and/or layout) dominates typical JavaScript processing time. Thus, it’s better to batch DOM updates from (potentially unrelated) application processes rather than to let each local process manipulate the DOM directly. (In fact, this is exactly what David does in his go block benchmark.)

Open questions

Core.async is definitely exciting, and I’ve been very happy using it in Clojure on the JVM to abstract ZeroMQ sockets and websockets. When it comes to DOM-based UIs, though, I have a few open questions.

What is the appropriate level of granularity for channels and go blocks? For each data binding (e.g., <span>My name is {{name}}</span>) you could have a local event loop blocking on a channel containing the latest value of name. This seems extreme to me; requiring a lot of multiplexed channel plumbing for seemingly little benefit over, say, a map within an atom. (I’d love for someone to take the conceptually elegant idea of “everything is a go block” and implement it elegantly in practice to prove me wrong.) Another phrasing of this question: how do view models / presentation models fit together with core.async concurrency?

How can local event loops be coordinated to batch DOM manipulations? If a single button click changes the application domain model that forces 5 conceptually independent views to update, can their updates be coordinated without over-coupling the views?

Do view widgets need objects/processes in memory when they’re not computing? Both Angular.js directives and go block local event loops take up memory in their “resting” state (Angular scope objects, go block stacks, and the native JavaScript event handlers with references to said objects and channels). What would a view widget look like if it only had a footprint in memory when it was being interacted with by the user or updating based on a change in application domain models?