> Immutable data structures are often the most helpful tool in accomplishing that.
This is said often but without much qualification. I've found mutable data structures with change propagation (via observable or what not) to work much better given that the whole diffing thing can be avoided altogether since you know exactly what has changed.
It is my understanding that the DOM is broken in how it handles invalidation/re-rendering (doing it for each modification rather than batching), but again, I don't see how immutability helps fix that problem any better than just doing it the right way with a mutable virtual DOM?
Most of the people that don't believe that immutable, persistent data structures are effective tools to increase performance of reconciliation, only put one foot in (that's my personal experience). That kind of reservation often compels people to "just try immutability on a part of their tree". It doesn't work like that and you often should go all in before you begin seeing the benefits. It's a leap of faith, admittedly.
Almost every application (ever) is a list of lists of lists (and so on). Even text can be broken up into paragraphs/code-blocks etc which form the lists. If these structures form a tree, and that tree is somewhat well balanced, then small changes can be found in log(n) time by comparing reference identity without developer intervention (by either a mixin or Om-like system). log(n) ends up being extremely fast for n in the range of graphical nodes in most UI applications. For everything else, a windowing infrastructure can be used (usually baked into a very sophisticated <ListView> component - our ReactNative mobile applications use this approach (special <ListView> component that exploits immutability without developer intervention)).
I work in this field and have written lots of code, both mutable and immutable, declarative, OO and functional, to solve a variety UI problems. I've also written my own language-enabled editors using multiple techniques (see comment https://news.ycombinator.com/item?id=9117234 for the one I'm working on right now, but you can see https://www.youtube.com/watch?v=SAoRWmjl1i4 for an Eclipse-based one I did in 2007), so I've definitely got multiple feet in the game.
I don't work in Web, most of my UI code was written for Swing, SWT (Eclipse), and these days WPF in immediate mode. It seems like React is solving a bunch of JS/DOM problems, so maybe my experience doesn't transfer, but I've found that for my work, it is much easier to just go with mutable data structures that support change propagation, so you make a change that affects a line in a block (my current editor architecture, block - lines - more blocks - more lines - etc...), the change is just...O(1) because the line can be damaged/repaired directly! So why would I give that up for O(log(n))?
It's a great question. All of this (immutability) only makes sense to even attempt if you believe that immutability is easier to reason about than mutability. If we don't agree there, then I have nothing more to add really. But assuming we agree, then there is the question of performance.
In most applications, we have three tiers of time durations.
Tier One: During an interaction/animation you must update the screen every 16ms. Code may not run longer than 16ms (less in practice).
Tier Two: You are not interacting, but may begin interacting with something at some unknown time. Code may not block the UI thread for longer than about 50ms so that there is a guarantee of not introducing perceivable delays into the interface.
Tier Three: Long running tasks which should be executed in parallel with the UI thread/process.
If going from O(1) to O(log(n)) still allows you to meet your deadlines for whichever latency tier you are targeting in whichever supported device classes you want to support, then it's worth it in order to program with better abstractions. Blocking for 1ms is as good as blocking for 13ms in Tier 1. Blocking for 25.5 is as good as blocking for 40ms in Tier 2. (This is helped by a decent event loop/task scheduler etc).
Again, all of this assumes you genuinely value immutability as a programming paradigm over mutability. If you'd really rather mutate, then you should just be using mutations/observables. I would not rather. Sometimes, I still perform mutations for the most critical parts of a UI abstraction (such as a scroller animation etc) - but I am up front about it being a compromise of what I'd rather do.
> All of this (immutability) only makes sense to even attempt if you believe that immutability is easier to reason about than mutability. If we don't agree there, then I have nothing more to add really.
Ok, that makes a lot of sense. I spend a lot of time trying to make mutability easy to reason about (my research), so I agree on the problems but disagree on solution strategies (fixing mutability with managed time vs. avoiding it). There are plenty of differing and evolving opinions in this field (e.g. back in the early 90s, constraints were going to save us).
I see as the main benefit of React is that there is no need for change propagation, just do a fast diff at the end. That's interesting and could be an overall win given the constant overhead of change propagation (O(1) with lots of bookkeeping), but I'm not sure how that would scale in practice. One of the reasons I moved on from declarative UIs (I did my dissertation on one called SuperGlue) was because the paradigm is difficult to scale in expressiveness (let alone performance) beyond simple examples; e.g. I couldn't build interactive compilers bolted onto seamlessly onto editors for richer programming experiences.
Ah, it's good to know we have an ally against a common foe and I respect that you're taking a different approach (I suspect you'd be interested in Mezzo). I'd really encourage you to try out React though. It's different than other FRP type systems because the granularity of "reactivity" is much larger (at the component level) which means the majority of your code executes on plain data structures such as arrays and objects. There's no need to continuously be lifting your data into some "reactive" data container. For example, if you have two numbers that are changing over time, if you want to add them, you use the + operator instead of creating some kind of "ReactivePlus" operator.
Also, in some types of apps, when you have many of these point to point bindings wired up, the bookkeeping of them can start to add up too, especially when everything ends up changing any time anything small changes. This hurts two cases predominantly:
1. Your initial rendering of the UI. You usually have to set up these point to point bindings. It would be faster to not have to when blocking the initial user experience (which is critical).
2. When small changes end up rerendering the entire page anyways. This is the worst time to be slow because you already have so much to do! If some small imperceivable delay becomes a medium imperceivable delay, it's not so bad. But when the entire scene changes all the time, a framework like React that anticipates this can cut right to the chase and do the rerendering without also having to do the bookkeeping along the way. Different apps will have different sweet spots in different paradigms. I will say that it's worked out well for us at Facebook/Instagram and many other serious production apps (not just simple examples). I'd encourage you to try it out and give us more feedback since you've done so much research in this area.
and it would compile that into a continuous data binding expression (auto lifting was also useful for defining shaders in C#, they use similar techniques in JS/WebGL). But you are absolutely right: the book keeping was too much, and if you had say a chart with 100x100 cells each individually bound, your startup time would really suck. Glitch (my current work) has a much lower cost per state read (where you install listeners), and, like React, does not require lifted operations: so you read some state into values, operate on some values, and store the values in some state somewhere else. Each read is traced as it happens, each write is logged as it occurs, everything in between is just normal code; no lifting is necessary (I think React is like that for reads and doesn't allow for state writes outside of DOM updates that appear immutable).
The only question now is about granularity, and that is completely tweakable. There are some issues with state cycles, which have to be rejected (otherwise, non-monotonic changes might not work correctly in an incremental context), and keeping state separate helps prevent "false" cycles from occurring, but I'm looking at ways to separate cycle detection with state update granularity, and anyways, none of that applies to React since writes aren't handled in the framework.
What I'm interested in is expressiveness; as a PL person I have a cliche benchmark: can you implement a compiler in React? A compiler, after all, is just a "view" of flat text into some other form (e.g. a typed AST that can be used as a model in a language-aware editor). For React, I think the symbol table would stop you (everything else in a compiler is pretty much functional), but I might be wrong.
I'm definitely interested in React and will keep looking at it. Unfortunately, I don't do any web work so finding a proper context is hard.
Your argument seems to stem from immutability is easier to reason, therefore you are willing to give up performance to accommodate it.
You stated that in some cases it doesn't matter if you use 1% or 100% of processing time in the current frame which I would argue is risky.
The more complex your application the more expensive handling wholesale changes becomes, unless you have something like react managing change detection you are going to suffer. From my point of view this is the interesting part of react, you hand off the task of change detection to generic, tested code. However it's still going to be slower than the alternative for fine grain changes.
It's also worth noting that on larger applications where you modularise common components, you typically push complexity down the chain. Take a data grid for example, do you process the data for every cell up front, or do you do it only when the cell is rendered. If it's the latter mutability of that data structure is really important, otherwise one little change is going to cost you far too much.
Holy cow this may be the most salient explanation of these various concerns I've seen.
Blocking for 1ms is as good as blocking for 13ms in Tier 1.
+1. Let's use all the resources we have available, and also make sure we understand where those resources are coming from. If one of our resources is user perception time we need to manage that just as we manage memory and CPU usage.
If the DOM itself is mutable, a primary philosophical problem I have with layering an immutable abstraction on top of it is that the inevitable leaks from things that lie outside of the immutable abstraction are hard to deal with.
It certainly makes sense to me to use immutable approaches in pure abstractions, but the world is mutable, so you have to be extremely clever to abstract that away.
Also as an application gets more complex, it may be harder to keep the juggling act of mutable structures going, and keeping them as fast as they were before.
Mutable structures have a tax that you often must pay with complexity. Immutable structures and React’s declarative approach have more of a flat tax, with means complexity and performance are much more predictable.
> Immutable data unlocks powerful memoization techniques and prohibits accidental coupling via shared mutable state.
Memoization is still more expensive than just tracking changes and avoiding unnecessary recomputations directly (it only starts winning when doing dynamic programming). There is a point on accidental coupling but this is more of a correctness rather than performance issue.
In the high-performance computing field, use of immutable data structures is suicidal; even tries are a magnitude slower than in-place mutable data structures. And he only compares against naked shared mutable state, not against managed mutable state with change propagation.
And that gets to the end of the talk: the real reason is they want to avoid using frameworks that already solve this problem by tracking changes, and React somehow avoids that since you can do just the diff post facto. Ok, I get that.
Yes, but immutability means that reference checks are enough to spot unchanged areas. And we're not in HPC land right now, we're writing JavaScript. In that world, Om (immutable) is vastly faster than Backbone, Knockout, Angular, Ember &c (mutable). Partially because, when you can reason more clearly, it's easier to do something about the performance.
Reference checks are conservative in spotting unchanged areas (if true, definitely no change, if false, maybe no change) unless all values are internalized (a bit expensive to do that). Also, diffing is only needed at all when you need to compare values anyways; dirty bits are otherwise sufficient to mark changes.
I'm not really familiar with the web ecosystem, but why would it differ so much from say C#/WPF? or a system based on change propagation instead of diffing?
If everything is immutable then a reference check is all you need. They key is to avoid deep copies. Observables are problematic when changes can ripple through your model/view-model - resulting in multiple DOM changes. This equally applies to WPF.
WPF has a retained scene graph, so no diffs are necessary, all changes are just O(1).
Reference equalities only work to know what really hasn't changed, they of course can't tell you that two values are still equal even if their references are different (unless compketely internalized, of course). For react, that's fine: it's just some extra work if false inequality is encountered, there are other applications where its not ok.
> This is said often but without much qualification. I've found mutable data structures with change propagation (via observable or what not) to work much better given that the whole diffing thing can be avoided altogether since you know exactly what has changed.
The problem with this though, is that the entire framework would have to be written around this idea, and everyone who uses the framework would have to use these datastructures correctly. That being said, it will probably be both more efficient and relatively easy to use once Object.observe lands in Ecmascript 7.
In the meantime React works with every datastructure out there, which is a big plus.
Immutable datastructures, while more expensive to change, could be very cheap to diff, because you know if two objects have the same pointer, they're equal.
> The problem with this though, is that the entire framework would have to be written around this idea, and everyone who uses the framework would have to use these datastructures correctly.
That is the problem, and one of the reasons why I haven't ported Glitch (works in C#) to Javascript yet (instead, opting to wrap it up in a new language).
> Immutable datastructures, while more expensive to change, could be very cheap to diff, because you know if two objects have the same pointer, they're equal.
Yes, I use this property a lot in my own code; I'm pragmatic and use both mutable/immutable data structures. That being said, I find it easy to trace changes and do change propagation on a mutable data structure (that can change) vs. an immutable one (which obviously require diffing since you can't know exactly what changed). Using immutable data structures actually make that problem much harder from my point of view, but I see its utility as an easy way to integrate with existing code and programming practices (something I can't offer).
The true speed of using an immutable library for your data in React comes from shouldComponentUpdate. Using immutable structures is really fast as the most deepest comparisons are always O(1) because all you're doing is comparing memory addresses, thus a component can very cheaply work out whether the data given to them has changed, and as little as possible can be recalculated.
This is said often but without much qualification. I've found mutable data structures with change propagation (via observable or what not) to work much better given that the whole diffing thing can be avoided altogether since you know exactly what has changed.
It is my understanding that the DOM is broken in how it handles invalidation/re-rendering (doing it for each modification rather than batching), but again, I don't see how immutability helps fix that problem any better than just doing it the right way with a mutable virtual DOM?