6 ms·
Reactive Programming and MVC (2014)
- dmitriid 9y agoA few problems I personally have with Rx are: - 99.9999% of examples and tutorials on the internet fit one page of code. It's enough to look at something more complex, and you get immediately lost, because: - Rx is very good at modelling streams that go in one direction, from A to B. From B to C. The to D etc. The moment you need the results of computation in C back in A... Oh, yikes. You start having Subjects everywhere (or BehaviourSubjects? Or ReplaySubjects? Or AsyncSubjects?) which may and will (if you are just starting learning these things) become extremely messy - Rx may be easily viewed as async programming. Because your transformations happen at some unspecified non-deterministic point in time (and a lot of operations are async). So, debugging is hell. RxJS and (IMO) RxJava have tried to improve this, but it's still a mess. Properly structuring your code and streams so that you understand why and when a certain something happens? No one teaches you that. And you need that because your `.map(transform)` has no idea when and who invoked it. Etc. - it doesn't help that Rx has cold and hot observables that everyone gets burned by. And I mean everyone. Oversubscription? Undersubscription? Events not firing? Events firing multiple times? These all happen. And some stream libraries go as far as not having cold observables at all. Good article about that: https://staltz.com/cold-and-hot-callbacks.html https://staltz.com/cold-and-hot-callbacks.html - even such a fundamental thing as the concept of time may be screwed. See a primer on schedulers: https://staltz.com/primer-on-rxjs-schedulers.html https://staltz.com/primer-on-rxjs-schedulers.html - My favourite thing: everyone tells you how easy and simple Rx is: it's just observables. In his book on RxJava the creator of RxJava says that it took him several months to understand Rx. While being tutored by one of the creators of ReactiveX. It's "easy and simple" in the same sense as "Haskell is easy and simple" or "rocket science is simple and easy" or <any branch of human knowledge> is simple and easy once you know and understand it. All in all, streams of things are a good idea. Splitting your work into small isolated side-effect free chunks of work is a good idea. With Rx though, unless you are bright yourself or you have someone to mentor you, Rx is an unassailable bastion.
- harperlee 9y agoPoints 2 and 3 seem (to me, I hope I'm not misunderstanding you) to be "fixed" by clojurescript's re-frame. You get a single queue of events that you define, based on what is relevant for your application, and you structure your app to cascade based on that stream. Creating a loop is some part of the app adding an event to the queue. And you are forced to keep functional code that only stores state in the app store tree. So you can easily know which event sequence makes a bug appear.
- aaimnr 9y ago"- My favourite thing: everyone tells you how easy and simple Rx is: it's just observables. In his book on RxJava the creator of RxJava says that it took him several months to understand Rx. While being tutored by one of the creators of ReactiveX. It's "easy and simple" in the same sense as "Haskell is easy and simple" or "rocket science is simple and easy" or <any branch of human knowledge> is simple and easy once you know and understand it." The problem here is that "simple" and "easy" are two completely different concepts. "Simple" is absolute, "easy" is relative. https://www.infoq.com/presentations/Simple-Made-Easy https://www.infoq.com/presentations/Simple-Made-Easy
- dkersten 9y agoRx is neither simple nor easy, for non-trivial projects. Its an incredibly leaky abstraction and you end up having to understand the internals to do non-trivial things. Understanding when something runs in what thread (and in RxJava knowing when to use subscribe/subscribeOn/observeOn was much harder than it claimed to be), how to correctly handle errors, retry failed operations, apply backpressure without dropping data — these things essentially force you (in my experience, at least, but I’m no Rx expert, just used it for a few months) to dig into the internals to understand how they work: ie not simple. But because of its lack of simplicity, it was also incredibly hard to use, to make it do what you want. So it was neither simple nor was it easy. (And yes, I buy into the differences between simple and easy)
- dkersten 9y agoPoint 2 (and the difficulty of creating backpressure[1]) is what made my team rip RxJava out of a project and replace it with something resembling clojure’s channels (well, really queues that can be “closed” which would unblock any thread waiting on it). Awkward error handling too (errors typically killed a stream, re-establishing it was not always easy). We needed complex retry logic, which meant we needed to detect errors and funnel the error values back to the source to be retried later, after some timeout perhaps. Maybe we were using RxJava wrong or weren’t knowleadgeable enough, but that’s part of my point: all of this was really hard for us in Rx and really easy once we could connect our logic through queues. Our code after ripping Rx out of the codebase looked quite similar: you could do queue.map(..).filter(...).moveTo(otherQueue) an so on. Its not identical to Rx, but its close enough that it only took 2 days to replace Rx with this, even though the project was quite large. You shouldn’t have to be an expert in Rx to use Rx. After about 4 months of using Rx every day, I was actually more confused than I was on day 1. Another issue we had with RxJava was that stacktraces almost never pointed to where the error actually happened in code, because the context of where the error happened in code is the context of when the streams are set up, NOT the context of where the streams are executed (and RxJava’s constructs are incredibly deeply nested too, making the stacktraces huge). We looked at a number of alternative libraries, but they were all too complex for our needs so we ended up writing a simple wrapper around queues that allowed us to “close” the queue (unblocking anything waiting on putting or getting) as well as to “complete” the queue, which would close the queueu for new puts but not for gets until its been drained. This took about a day for the initial implementation and two days to refactor our codebase to use this. > - My favourite thing: everyone tells you how easy and simple Rx is I joined the project and thought “wow! Rx is so simple! I love t!” and three months later, I hated Rx because of how complex it actually turned out to be. Rx is NOT simple for real world needs (back pressure that doesn’t drop data, error handling, retry logic)... [1] RxJava has built-in backpressure support, but it was quite limited unless you jumped through hoops to implement your own data sources (sorry, I don’t remember the lingo). You could easily add throttling that would drop values, for example, but we found it really hard to have a->b and have a block until b can accept more. We first wrote a queue-based thing that would work with Rx since the queue would block if full, but it didn’t play well with Rx in the face of errors (if the stream died, then items on the queue got lost, iirc) and it was really hard to fix. Ripping out Rx and replacing it with our own little thing fixed all of these issues for us.
- ivarru 9y agoDisappointing. The title should say (2014). I would say that this field has matured a bit in 3.5 years.
- sctb 9y agoThanks, we've updated the title.
- bsaul 9y agoThe rx fallacy is to make you believe that programming at the most general abstract level is the best. It’s just wrong. An array is not a stream, an observable that can fire at any time isn’t the same as a predefined list of things. Whenever you abstract a data structure, you loose some meaning, some peculiarities that helps you understand what you’re doing. Rx should be used by framework or low level libraries developers, to factor code. But it should be almost invisible to the api user.
- coldtea 9y ago>The rx fallacy is to make you believe that programming at the most general abstract level is the best. It’s just wrong. Citation needed. >Whenever you abstract a data structure, you loose some meaning, some peculiarities that helps you understand what you’re doing. That's obviously not true. The whole idea is to abstract over peculiarities that are not needed (implementation details for example). So, you "loose some meaning, some peculiarities that helps you understand what you’re doing" only some times (when you abstract badly) and not "whenever you abstract a data structure". >Rx should be used by framework or low level libraries developers, to factor code. But it should be almost invisible to the api user. That makes even less sense - having abstraction as the hidden lower level. The whole point of abstraction is giving a higher level interface.
- bsaul 9y agoAbstraction is, by definition, reducing different things to a shared subset of their attributes. Eg : the concept of "Human" is an abstraction over all the different attributes of all the individuals, to reduce them to the ones they all share. If i say i met a "human being", you can't tell if it's a "he" or a "she", if he has dark of blond hair, etc. What you can tell, is that it has a heart and a brain, stand on at most two legs, and has at most two arms. That could be all you need to deduce a few things, but by saying so, i kept some details hidden from you. Now, if we get back to rx, its whole purpose is to work with the concept of "streams of events". Noting that many data structure and patterns (such as iterators over arrays, or event source observation) can be thought a specific kind of even streams. This lets you define stream-combination operations, that work on all those structure. Which may, sometimes, be convenient. The problem is when people start telling you that the generic tools around Rx Stream (producer, consumer, publish subject, etc) are the best tools to model the behavior of all your application, because "everything is in fact one big giant stream of events". And then you end up with code monstruosities, because you realize that it's more complicated than that. Maintaining a UI state and mutating it becomes a nightmare, you start wrapping every native structures (such as arrays) into Observers just to perform the equivalent of a join, etc. Either the stdlib and frameworks are "reactive", and then using rx pattern would be invisible, or you're going to have a horrible code. In some way, it's exactly like generics, or base superclass : you define them in the stdlib, for example to share code between Sets and Arrays, but what the end user is manipulating 99% of the time isn't "Iterable". It's Arrays and Sets.
- kylecordes 9y agoTo rephrase a quip, I have found often that RxJS and similar tooling is the worst way to get correct behavior... except for all the others. More than once I have set down a path to avoid RxJS, because of the complexity of explaining it and bringing a new developer up to speed... and ended up writing more code and having more difficulty producing correct results. My context is in web applications, SPAs. Typically with Angular, but I've used many tools in this space, including React, Redux, Cycle (which has its own Rx-like library), etc. The tricky part in all this is pervasive asynchronicity. Here's an example. Looking back at the AngularJS era, most of the applications I have seen (including review of many of our big customers applications) have incorrect behavior in the case of servers with jittery response time. It is quite easy to make an event handler load some data, and miss the fact that the data arrives in a different order than it was requested. Very easy to avoid with RxJS. If anyone's interested in this bit, here's a video explaining the merit of RxJS even with AngularJS 1.x, before the adoption of RxJS in Angular 2+. https://www.youtube.com/watch?v=XY9VPrKuJaA https://www.youtube.com/watch?v=XY9VPrKuJaA
- dfee 9y agoI "learned" RxPy in a couple hours yesterday (a 3rd party lib I'm using has an interface that requires me to use them), and I figure I'll share a bit about my experience: 1) the documentation is supremely lacking (subjects aren't even mentioned in the RxPy repo https://github.com/ReactiveX/RxPY https://github.com/ReactiveX/RxPY) 2) the generic documentation points to language specific documentation – that's 80% just "TBD" (http://reactivex.io/documentation/subject.html http://reactivex.io/documentation/subject.html) 3) it's actually quite simple once you figure out it's just code level pub/sub with a new naming convention. 4) there seem to be some opinionated perspectives around using it in the community, though largely from a JS perspective (look into "should I use subjects"). Despite those flaws, I've found it pretty accessible, and I'll actually switch out some home-grown code for it. [edit] I noticed some other comments complaining about execution handling (threads, etc.) so I figured I'd update this and say, maybe I got lucky as it's all pretty straightforward in Python (as long as you understand your app's execution environment (threads, asyncio, etc.) -> https://github.com/ReactiveX/RxPY#schedulers https://github.com/ReactiveX/RxPY#schedulers