Pros and Cons of using immutability with React.js(reactkungfu.com) |
Pros and Cons of using immutability with React.js(reactkungfu.com) |
Another option is a library I wrote, react-cursor[1], which is basically sugar over the React immutability helpers[2] which the article mentioned. react-cursor has a couple advantages over immutable-js and baobab:
1/ simpler types: use regular react state with plain old js data structures, 2/ simpler implementation - about 100 lines of code and tiny api, 3/ super easy to integrate with your existing large codebase already using react state
[1] https://github.com/dustingetz/react-cursor [2] http://facebook.github.io/react/docs/update.html
One problem I have is that ImmutableJS[1] doesn't list the complexity of any of the operations in their documentation. So it can be hard to intuit the efficiency of given operations without having read/grokked the 5k sloc of source.
Is it blasphemous to say that looking at runtime complexity is gradually becoming more of a premature optimization (thanks to better hardware)? You can argue all day long that your js object has constant insertion time, but the underlying implementation makes it an order of magnitude slower than array for a limited number of fields. And if you accidentally trigger the hidden class deopt that turns it into a hash map that's another order of magnitude slower. No amount of ordinary complexity analysis will help you here. Vice-versa, when Babel gradually starts supporting constant lifting for collections (somehow), you can look at a piece of code in your editor, reason that a comparison is linear, but then have the transpiler lift it out (`const liftedA = []; function foo() {return liftedA;}` instead of `function foo() {return [];}`) and not realizing yourself that the comparison is actually constant time (reference comparison). And then, if you write some overly clever optimization for that piece of code yourself, you might ironically get worse perf because the transpiler can't lift the collection anymore.
That being said, Immutable-js uses the same concept and clojure's persistent data structures (exposed as mori for JS users). Here's a nice article on it: http://hypirion.com/musings/understanding-persistent-vector-...
This resonates with me. I write a lot of Scheme, and often enough someone comes along saying that association lists (simple lists of pairs) are terrible because lookup time is linear and that I should be using hash tables. However, they don't realize that hash tables are only faster when the mapping is very large and come with a penalty of no longer having a persistent data structure.
EDIT: Though, I personally think it actually would be pretty helpful if ImmutableJS put something like this in their docs: "it's linear theoretically but most of the time it's really almost constant time"
[1] http://gaearon.github.io/redux
[2] biggest flaw: if an action updates two different stores, and a component depends on both stores, the component will get rendered twice. Even worse, the first time it will get rendered with one store updated and the other one not updated, so possibly in an inconsistent state.
At this point I really get the impression that redux has "won" now that flummox recommends using redux instead of flummox.
Flux libraries are small and simple enough that it probably won't hurt to use a non-mainstream flux library, but it's probably still best to use the same one everybody else is using.
Lists are immutable and fully persistent with O(log32 N) gets and sets, and O(1) push and pop.
Immutable Map is an unordered KeyedIterable of (key, value) pairs with O(log32 N) gets and O(log32 N) persistent sets.
A Collection of unique values with O(log32 N) adds and has.
etc.
yourCar === neighboursCar; // false
yourCarRepainted === yourCar; // false :(
In the article's examples, how does the program know which object is different? It's weaved into the language design that every object has an address. That means if we want to write code without mutability, the interpreter has no idea since mutability is always on, and can't protect from accidentally modifying intentionally stateless code.Flip side, if immutability is the default, we can easily add state, since it's just a lack of an address. The address becomes part of the data structure, giving the coder more power, since it's not locked outside of the code. Think SQL! Or Haskell!
var yourCar = {id: 'my_car', color: 'red'},
neighboursCar = {id: 'neighbours_car', color: 'red'};
function referenceEqual(a, b) { return valueEqual(a.id, b.id); }
referenceEqual(yourCar, neighboursCar); // false :D
var yourCarRepainted = Object.assign({}, yourCar, {color: 'red'});
referenceEqual(yourCar, yourCarRepainted); // true :D
Notice we've now flipped the address into our data, and even the (===) operator in our hands. With JS, this will run slowly since it can't infer our immutability, but constants are coming! const newState = {...state, ...objectWithNewValues} (state, action) => ({...state, loading: true})
(state, {error}) => ({...state, error, loading: false})
(state, {result:{data}}) => ({...state, ...data, loading: false})A con when updating tree structures is the need to replace all nodes along the path from the root, which is why functional languages sometimes use fancy data structures like lenses.
Graphs are a bit awkwardly represented (can't use regular pointers due to cycles).
Reference comparisons are fast for small nodes, but for something like a large list, it's often not enough to know it was touched. You need to compute the diff to make updates efficient, which often requires a linear scan or worse. Making this efficient for arbitrary list mutations is a fairly difficult problem.
I think you mean zippers. I'm not sure if lenses can be called data structures. They are closer to Functors than say Linked Lists in nature (but I may be wrong here).
In this fundamental React render flow, you probably won't need to worry about checking value equality between two different objects if you are using immutable data. Now, your application might have specific UI requirements that need to do value equality. As a random example, you might have a complex form, and you might want to know if, after the user changed several things, the form's data is different than it was at the beginning. In this case, you'll have to do a (more expensive) value equality check.
- writing functions that expect immutable data (you get referential transparency and value equality → a system that's easier to reason about) - using persistent data structures (makes it cheap and efficient to create new changes to your data over some messy Object.assign helpers)
Javascript doesn't promote applications written in that style though, so you're definitely going to want to use a library like Immutable.js everywhere for those kinds of guarantees.
x == y; // true
x === y; // true
without a single instance where the results differ. This would be stronger if the first line of every pair were removed, since this isn't an article about JS equality operators.