I don't know about RxJS, but the shipped/production .NET Rx is built on "Px", the Parallel Extensions. One talk on Channel 9 suggested that the Task abstraction and C# 5's async/await provided a substantial complexity and code size reductions.
Although C#'s compiler implements async functions as a switch-statement-state-machine, I think Clojure(Script) would prefer a CPS transform. In my mind, the path towards an FRP GUI looks like this:1) Implement reliable CPS transform
2) Generalize "promise" to "task" which is just a promise with a continuation callback3) Implement async, defasync, and await (warning: name conflict for agent's await)
4) Define observable/observer/etc abstractions
5) Implement higher order reactive operators
6) Figure out how the heck to make this all work with the DOM
But do we need all that? I'm very hesitant to take anything MS does as
"the best way" or even as "a good way".
Look for example, at the complexity of IObservable<T>, IObserver<T>
and IDisposable<T> compared to Paul's Pub/Sub example. Now, if there
is a reason for that complexity, that's fine, but we should do
ourselves the favor of closely evaluating each and every aspect of an
implementation before we just jump into it "whole hog".
Let's remember that Rx is built for C# (and VB.NET) and C# is an
imperative, mutable-by-default, language with no concept of time.
I question how much of C# we should pull into Clojure, if any at all.
And this comes from someone who spends about 8 hours a day writing C#
apps.
I dug through some Px stuff as well (all the citation material listed in the Rx readings), but I didn't study anything in depth. Is there anything specific I should read?
I don't want the approach to be tightly coupled to a strategy. We should be able to decide: async via CPS or JS events, synchronous, http web messaging backed, etc.
Tightly coupling to an implemented strategy is going to hamper future proofing and limit the reach and effectiveness of the system. Making the underlying infrastructure as open for extension as possible needs to be a design goal.
Define observable/observer/etc abstractions
What do you think are the tradeoffs in centering the abstractions around that and building the reactive pieces upon those abstractions?
5) Implement higher order reactive operatorsWhich of the higher-order reactive operators do you find yourself using most (or would you envision using most).
How are these composable with non-reactive higher-order functions?
6) Figure out how the heck to make this all work with the DOMI actually think this is the one we all feel is nailed down
Lamina implements most of what Brandon was advocating from the Rx
point of view (including a async macro). I'm not exactly sure how easy
it would be to port to cljs, but it looks like it would take a fair
amount of time.
It might be useful to put together a Confluence page that documents /
summarizes the various topics / approaches covered in this thread.
David
Over the past week I've done some more reading up on async, Task<T>
After some more thinking on the issue, my DSL solution won't work with
a Pub/Sub implementation. This is due to the way lifted constants are
handled.
In the current code, when we lift a constant we are basically
creating a node that allows observers to attach, but the moment they
do, the lifted variable fires off an event. In the Pub/Sub solution we
can't do this since we don't know when subscribers attach to our
topic.
In the end we have three types of problems we're trying to solve here:
1) single value bindings. Think of this as my original example. We're
only interested in the current mouse position
2) event streams, if the user clicks a button three times we may want
all three clicks
3) async events. An auto complete box is a good example of this
1) a library for handling event streams (easy enough on cljs due to it
being single threaded)
2) a way to integrate async tasks into this pipeline. On cljs this
could be as easy as extending (task) to support IObservable
3) a DSL to tie it all together.
At first I was against Rx since it is a bit convoluted for a simple
single value bindings. However, once we introduce async calls into the
mix (and we have no other choice in the browser), we are now looking
at a different paradigm.
I started thinking more about Task, `async`, and Brandon's request to change promise.
I'm coming to the conclusion that I think promise should be left as is, but perhaps we need something like clojure.core.control - continuation based control flow functions and CPS transformations. Thoughts?
I've been keeping a close eye on Chris Frisz's clojure-tco. Chris is one Dan Friedman's students so I suspect this will eventually (if doesn't already) qualify as a robust CPS transform.
I agree, complex lazy-seqs, async, and lightweight actors are all quite easy once we have proper CPS transforms. CPS does seem to be a common need behind all the ideas we've come up with, so I agree, we should start there.
I'm not quite read up enough on CPS to understand the clojure-tco code. Does anyone know if it's generic enough to reuse? TCO seems to be a subset of general CPS.
My only concern is that I think it uses a custom AST representation. It would be nice to finally wrap up what we want CLJS's analyzer to produce and then see if the CPSer in CLJTCO can be made to work with it.
My only concern is that I think it uses a custom AST representation. It would be nice to finally wrap up what we want CLJS's analyzer to produce and then see if the CPSer in CLJTCO can be made to work with it.
My suggestion is the same as Brandon's, but a step back. Let's forget about async first - let's build out proper CPS transformations and delimited continuations, then build on top of that (limited `yield` coroutines, `async`, etc.)
Task generators are split into two groups - "hot tasks" that run immediately (ie: futures) and "cold tasks" that must be started explicitly (something like `delay`)
These tasks are continuation-backed units of work (akin to delimited continuation capture). Once you have that foundation, you can build everything else with it (we could even introduce a pool of green threads if we really wanted to).