Discovering and mastering bacon.js/FRP completely changed the way I think about programming. It hugely increased the quality and stability of my code in ways that continue to surprise and amaze me. I cannot recommend it enough, but this article definitely has me curious about ClojureScript.
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?
I wrote a 20k LoC bacon app in 2014 and had the same issue with being able to understand all the pieces individually but having trouble comprehending the whole system.
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.
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]
Happy to hear that. I'm not actively using it and I find the the docs are fairly good but written from the implementor's perspective so they tend to bury the lede. I didn't think it had the equvalent of Reagent's reactions until I got towards the bottom of the react-redux page, which was the third or fourth time I looked at the docs. I'm not sure if I just missed it or it got added after I last looked.
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).
This sounds like an interesting approach, though I'm not really fan of events (it's easy to see when they fire, but less easy to make sure anything is listening).
You could try applying The Elm Architecture [1]. It's developed for the Elm programming language [2], but I've seen more than one framework outside of the Elm ecosystem cite it as inspiration for their structure.
CycleJS, which is based on RxJS and smaller than most of the "full blown" frameworks and SPAs, I think has a good approach to code organization in its MVI (Model-View-Intent) documentation:
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.
I wrote a post on using RxJS as a model layer with React a while ago (http://aricedev.com/building-a-model-layer-with-rxjs/). The biggest change I've made since then, aside from moving to immutable data structures, is that I'm using a single Rx subject in each store to delegate to the appropriate streams instead of having a subject for each stream.
The Clojure, and ClojureScript, hype train is effective. I've been interested in learning Clojure for several years. Every other week, or so, I'll see a tweet or blog post that makes me want to drop JavaScript and move over to ClojureScript. The community looks like a great one to be part of, from an outsider's perspective. With that said, I've never been able to break beyond that learning curve, or have that "ah hah!" moment. I've been able to learn a lot of different languages over the years, and I so want Clojure to be one of them but it just hasn't happened for me, yet. This blog post puts my issue into words very well, it feels like I'm trying to program in mandarin and I struggle to be efficient at what seem like the most basic of tasks. A script that I could knock out in 20 minutes, with C# or JavaScript, will take me hours or days in Clojure because of the lack of muscle memory or something. My excitement always fades back into thoughts of "meh is it really worth the effort", at which point I usually just revert back to JS. :(
RxJS has a steep learning curve and is quite hard to really understand beyond basic examples that fit on a presentation slide.
It might be a great tool for really smart developers, but I wouldn't share any large RxJS code in a developer team where all developers are not at least 200% geniuses.
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).
I don't fully understand the argument against FRP here. It seems like the author has passed on FRP in favor of coroutine-like asynchronous programming. I don't view one as replacement for the other, but as different layers of an asynchronous programming system. Coroutines are great for async and imperative tasks, while FRP is great for async and functional tasks with persistent data. My own FRP implementation [0] in Guile Scheme is built on top of a coroutine implementation [1], which in turn is built on top of Guile's first-class delimited continuations. [2]
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.
RxJS will automatically dispose/unsubscribe in composition scenarios: for instance, if you flatMap from one "underlying" observable to a sequence of observables, when you unsubscribe from the "underlying" observable it will observe any remaining (if any) hot observables that were kicked off by the flatMap.
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.
>RxJS will automatically dispose/unsubscribe in composition scenarios
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.
Thus far I've only had to manually dispose a single RxJS observable, and that turned out to lead me to a better composition strategy and I got rid of the manual dispose.
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 ...
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.
I'm not a core.async/UI wizard but have a look at this [0].
It's a tutorial for building a reactive ToDo List application using core.async for event processing, crate [1] for rendering and jayq [2] for DOM manipulation and ajax calls.
if you don't mind me asking, how do you really learn about the different models of concurrency/parallelism well enough to be able to compare them based not only on API/programming model but also performance and low-level differences?
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?
I see (and use) core.async as low level primitives from which to build higher level FRP, reactive, flow-based or message-based systems.
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.
As soon as OP started talking about Go and Clojure and Core.async, I wondered if Scala.js would give him the same sort of benefits in this case as ClojureScript. But I don't know a lot about Scala.js yet and if you can actually run an actor system on the javascript frontend.
You can run an actor system in Javascript of course and there are partial ports of Akka available, but are not meant for production use. I wouldn't use Akka in the browser though, as it's too heavy and I feel that the kind of tasks meant for Akka do not happen in the browser, though for Node.js that would be another story.
If you feel that RxJS is a bit hard, but don't want to go all crazy and switch to ClojureScript; then Redux is pretty good. It's not as "powerfull" as RxJS and it has some guidelines on how you should structure code. It also have very good tooling integrations, and debug-ability.
Once I found flyd [0] I never looked back at any of the other streams libraries. It's very simple, easy to jump into, and doesn't have the stream/property split. There's not a lot of code, so it doesn't make debugging any harder.
Also, there is js-csp [1] if you want to use CSP without having to make the jump to ClojureScript. Works great.
The article mentions that async/await functionality hasn't landed in Javascript yet. I wonder if you can get the same functionality today using scala.js [0] and the scala/async [1] library.
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.
I have been using yortus' async/await library[1] for a simple personal project and honestly it works fine. I don't know if it's "serious/production" ready but it has a extensive README and examples.
That looks to me, a lot more complicated than it needs to be. I think that has combined two separate things - atoms and cursors. Here they are in javascript:
The essential idea of a cursor can be implemented in about 10 lines, and because I actually looked through the source code of mobservable, I understand what they are essentially doing and how it requires a couple hundred lines. Since we're talking ideas here and not implementations, measuring artifacts is not helpful.
My personal experience was that there is not a one-thing-fits-all solution. There are things that can be beautifully implemented with Rx, and there are other things were I found that using Promises (probably in combination with async/await) makes things clearer. My current project (which involes quite a bit of Typescript code) uses everything from Rx, Promises up to plain old node EventEmitters and streams, and I'm actually really happy with all of it.
I just built something using RxJS that is pull-based. The easiest way I found was just to have a timer that calls subject.next(), and the subject calculates its next value and calls this.onNext(value) which pushes to the subscribers.
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?