RxJS is great. So why have I moved on?
medium.com
medium.com
In the meantime, the biggest issue I've had with bacon is figuring out what are the best practices in organizing the code. One does end up with a lot of streams or variations/combinations on streams used for different purposes. Eventually it does become hard to follow, as the author mentions, though because all state is self-contained you can change any one part with a high degree of confidence that it won't break other features.
Still there must be some ideas out there that don't involve incorporating a full blown framework/SPA architecture. The author of bacon has a blog post on the subject but it seemed to me that there could be better ways still. Anyone have any suggestions?
[1] https://github.com/evancz/elm-architecture-tutorial/ [2] http://elm-lang.org/
I don't have non-architecture answers for how to organize code since that's pretty much the point of an architecture. Most of the application organization patterns around the React space are only ~200 lines of code and a description of how the pieces fit together. You don't have to adopt the whole thing immediately but you don't get the full benefits of a pattern until everything works the same way.
For my part, I got a job writing clojurescript full time and migrated the app to re-frame [1][2] this year in pieces over the course of a couple months and it's the first frontend architecture that's made me happy in over a decade of continuous searching. The js version is Redux but I think Redux is missing middleware as a concept.
[1] https://github.com/Day8/re-frame/ [2] https://github.com/binaryage/pure-frame <- Fork with no global atom
What this provides is a complete event cycle of completely pure functions (the state swap happens in the framework) with all the data in an immutable map in a single atom. By namespacing my subscriptions and events I can see a problem, look at the component, and know immediately where my problem is within ~10 lines of code. It's not perfect but it's pretty good.
One thing that doesn't get mentioned often enough when talking about Clojurescript is the potential for doing server-side rendering of the app without having to run node. [3]
[3] http://yogthos.net/posts/2015-11-24-Serverside-Reagent.html
Redux totally has middleware (you actually need middleware for async actions).
> I didn't think it had the equvalent of Reagent's reactions until I got towards the bottom of the react-redux page
Is that the subscription mechanism? I have yet to find a use for it...
The two use the same model but are organized differently so it throws me off a bit.
Re-frame chooses to hide the actual reduction step so instead of the big switch statement you have a bunch of registered event handlers, which are pure functions take the state and event and return the new state. The middleware is HoF around these so it's done per-handler as well as at the base reducer level, which turns out to be really good for code reuse.
> Is that the subscription mechanism? I have yet to find a use for it...
I think they call it selections. Reactions let you transform a normalized data model into a shape that's useful for the components. They also let you build up chains of reusable calculations/conditions to shift complex conditionals out of the components and into the model code so its co-located with the handlers, which are split up by the part of the app they work with (credentials, search, nav, etc).
http://cycle.js.org/model-view-intent.html
For a couple of projects I'm working on I've been slowly iterating towards one sort of deeper formalization of Cycle's MVI, but I haven't published any of that work yet.
The whole thing reminds me of C++ years ago, where the language allowed for really smart and efficient coding style, but many less skilled developers at the time had problems with deep understanding of templates, operator overloading, multiple inheritance, various constructors and such. Pragmatic companies (e.g. Mozilla) thus decided to ban all the advanced features and stick just to basic ones so that the code would be maintainable.
I think RxJS is facing similar fate. I've spent quite some time learning it and I think it is one of the more complex paradigms on the market today. In addition there isn't much useful documentation. Feel free to check stack overflow and find out about the struggles people are going through tying to extend basic examples (e.g. google for rxjs mousedrag).
The answer is to build less complex things.
That said, the author specifically talks about RxJs and Bacon.js, both of which have two major problems for me:
1) They don't satisfy the closure property. They both have two fundamental data types: event streams and properties. Certain combinators expect streams, and others expect properties. Compare this with API's like Elm's which just have a single type: the signal. The closure property is satisfied here and programming is much more pleasant.
2) They both deal with stream/property life cycles. Objects need to explicitly unsubscribe from other objects, and streams may have a beginning and an end (i.e. they may be marked as having no value or marked as being done producing values). I think this is a mistake that complicates the API. FRP objects should always have a value, have no notion of being done, and not require the equivalent of manual memory management to clean up. My Scheme implementation uses weak references to automatically unsubscribe signals when they are no longer referenced, which is basically only during development when changing things at the REPL. Bacon and RxJS can't do something like this because (and correct me if I'm wrong), no JS standard prior to ES6 has weak data structures. Anyway, after all the hacking, the final program has a static signal graph, just like Elm, which I think is the right way to do things.
[0] https://git.dthompson.us/sly.git/blob/HEAD:/sly/signal.scm
[1] https://git.dthompson.us/sly.git/blob/HEAD:/sly/coroutine.sc...
[2] https://www.gnu.org/software/guile/manual/html_node/Prompts....
RxJS also automatically disposes resources when an observable completes and maybe that's a part of what you are missing in the observable lifecycle and part of why you've felt that RxJS has an equivalent of "manual memory management"?
Yes, lots of streams are effectively infinite in nature, but that doesn't mean that all of them are, and a completion signal can still be useful and informative, including in lifecycle management. (It also helps keep the duality between Enumerable/Iterator worlds and Observable/Observer worlds.)
One obvious case that I see a lot in applications where finite streams/observables show up in great number is that Promise (Task/Future) is essentially an observable that produces one result and completes.
But it still requires manually unsubscribing from something in the first place. I just think that's the wrong approach to FRP. The graphs are actually static, but dynamic behavior is needed for developing at the REPL.
If I'm working in a REPL I set things up to "naturally complete" with some useful sample set, just as if I were working with potentially infinite enumerables. In RxJS that would typically be something like myObservable.take(5).
As a disclaimer I'm the author of an Rx-inspired library for Scala [1] and that also works for Scala.js in the browser. Shameless plug aside, Scala also has Future/Promise in its standard library and now due to macros support it got scala/async [2], a library that gives you the "await" keyword in Scala, so in Scala you also get this kind of M:N multithreading that looks like synchronous code. This in addition to Akka and other possibilities.
In other words I've worked with both approaches and Core.async is not comparable with Rx. I do understand the author's woes, as sometimes the Rx model is misapplied, plus it's hard to understand for the unfamiliar.
One of my colleagues was complaining once that "but I don't know what the debounce operator does and it's hard for me to read that". And I told him: yeah dude, but try implementing the logic in debounce by yourself and see how readable that is.
And that's exactly why Rx is problematic. On one hand because stream processing is fundamentally hard, no matter what model you choose. And on the other hand Rx comes with a lot of useful operators that do a lot for you, but then you have to learn about them.
For the naysayers, I'll just leave this piece of code with a challenge for implementing it with core.async and compare in terms of readability and note this is a copy paste from actual production code ...
commands
.groupBy(w => (w.assetID, w.commandID))
.mergeMap { gr =>
gr.timeout(30.seconds, Observable.empty)
.throttleLast(1.second)
.distinctUntilChanged
.echoRepeated(5.seconds)
.whileBusyBuffer(DropOld(30))
}
What it does is to split the signals for each asset and command, for each of these it's supposed to sample the signal by 1 second, but in case the same value is repeated over and over again or in case the channel goes silent, then the last value will end up signaled every 5 seconds (reducing the traffic to our OpenTSDB). Finally for each key we close the stream after 30 seconds of inactivity. And then for each such key it does buffering of at most 30 elements and in case the consumers are too slow, then these buffers start dropping older elements on overflow. And then we merge everything back.I would also show you how we are modeling state machines with the "scan" operator, state machines that are evolved from signals coming from multiple sources, but the sample would be too long. In any case "scan" allows you to use pure functions and data-structures, so you can test your business logic without interactions to third party services, mocks, stubs or whatever.
I have to deal with such code all the time. And I've seen such code implemented in a classic fashion as well. You basically end up with Maps storing stuff and with ifs and whiles and with manual timers in an unholy dance of mutation so hard to understand and debug that it would make grown men cry.
But then such solutions are not silver bullets. Rx, CSP, futures, actors are not silver bullets to be applied everywhere, with all of them having a sweet spot for which they excel. I'm actually using Rx, actors, futures, scala/sync in the same project and it's great.
Also, one last note: Rx is not FRP ;-)
Interesting, though I use re-frame [3].
[0] http://rigsomelight.com/2013/07/18/clojurescript-core-async-... [1] https://github.com/ibdknox/crate [2] https://github.com/ibdknox/jayq [3] https://github.com/Day8/re-frame
as an example,i want to build a web crawler. ive worked with akka actors, and that seems like a good fit because the messaging logic for scraping is simple and it gives me remote distribution for free.. but then i see all these other models and wonder - what is the difference in doing it with Rx or futures etc? is there any difference besides choice of programming model in terms of how these things actually map to low level primitives?
For example, re-frame, zelkova (Elm-like thing in ClojureScript[1]) and others (eg the in-house thing I wrote before I came across re-frame) are implemented using core.async
So, no, its not a substitute - its a (potential) building block.
Also, there is js-csp [1] if you want to use CSP without having to make the jump to ClojureScript. Works great.
[0] https://github.com/paldepind/flyd [1] https://github.com/ubolonton/js-csp
I believe it should work. The scala/async library is just a set of macros that transform blocks of code using async/await into for comprehensions.
[0] http://www.scala-js.org [1] https://github.com/scala/async
https://github.com/cjohansen/js-atom/blob/master/atom.js
https://github.com/dustingetz/react-cursor/blob/master/src/C...
Vastly less code than https://github.com/mweststrate/mobservable/tree/master/src. Cursor+Atom has nothing to do with Rx or core.async, of course, and is probably not the future. (I say this as the maintainer of react-cursor)
1 - https://github.com/dustingetz/react-cursor/blob/master/dist/... 2 - https://github.com/mweststrate/mobservable/blob/master/dist/...
I was expecting more pull based support.
Is this what you are talking about ? https://github.com/ReactiveX/RxJS/pull/138
It looks like its already merged in.