Immutable Data Structures That Are Compatible with Normal JS Arrays and Objects
github.com
github.com
- Purpose-built specialized data structures (Immutable.js, Mori)
- Libraries that use freezing in some way
- Utilities that abstract over immutably updating plain JS objects and arrays.
seamless-immutable falls into the second category. Looks like it also adds some extra methods to objects it wraps/returns, and overwrites mutating methods to throw errors to help you avoid them.
My Redux addons catalog has a large page listing all of the libs I've seen that fall into these categories [0]. If you don't want to use one of the specialized data structure libs or libs that do freezing, there's at least a couple dozen immutable update utility libs out there, with a variety of APIs to choose from.
My list also has a section of Redux middlewares that will help debug accidental mutations in development, either via freezing or other comparisons [1].
[0] https://github.com/markerikson/redux-ecosystem-links/blob/ma...
[1] https://github.com/markerikson/redux-ecosystem-links/blob/ma...
it allows to use immutable data structures in mutable-like way, using plain assignments: https://www.npmjs.com/package/transmutable (mutations are not performed but they are just recorded in ES6 Proxy, and object is cloned on commit (sort of copy-on-write), and mutations are then applied).
So it basically enables for writing such things:
const copy = transform(original, stage => {
stage.x = 10;
stage.y = 20;
stage.foo.bar = 123;
})
instead of Object.assign / ... mess.The only issue with it is, of course, the fact that Proxies are ES6 and everyone still has to deal with old browsers.
I know MobX does similar object observation, and they support IE, but I've never had the time to go into their codebase. Do you know if something like this could be written in a way that allows IE10/11 to support it?
Also, could you elaborate on why it's necessary to use a proxy at all, when in reality you could just do a copy of the object from the very beginning and allow them to do mutations on that?
MobX works by wrapping plain objects and arrays with its "observable" equivalents. Per [1] , its object support requires fields to already exist, so it knows how to generate wrapper fields accordingly.
As for copying: as I talked about in the "Immutable Update Patterns" section of the Redux docs [2], proper immutable updates require copies of _every_ level of nesting that is being modified. If you want to make an update to `state.a.b.c.d`, you need to make copies of c, b, a, and state. That's doable, but takes work, and can get ugly if you're dealing with nesting by hand. This does lead to frequent mistakes, like assuming that `let newObj = oldObj` makes a copy (it just adds another reference to the same object), or that `Object.assign()` does deep copies (it's only shallow, ie, the first level). It's one of the most common pain points I see for people learning immutability and/or using Redux.
What something like the `transmutable` lib appears to give you is the ability to write perfectly standard imperative mutation code with no extra fluff necessary, even for nested data, and still get proper immutable updates. I'm definitely going to have to play around with it.
[0] https://stackoverflow.com/questions/35025204/javascript-prox...
[1] https://mobx.js.org/refguide/object.html
[2] http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePat...
I may consider to switch back into getters/setters, if support of Proxy is really a problem (although getters/setters have its limitations).
There is also Proxy polyfill, although it has the same limitations getters/setters have ("properties you want to proxy must be known at creation time"): https://github.com/GoogleChrome/proxy-polyfill
The thing I like about immutability in languages that implement it by design is that you know that if you get an data structure, it is immutable. In the world of javascript, you not only have to worry about whether an object you get from a library is immutable, but which kind of immutable implementation is using. Moreover, that information is not easily available in the type signatures, so you have to go to the library author's documentation, if any! That, to me, is a complete mess.
my_list = [1,2,3] # mutable
my_tuple = (1,2,3) # immutable
my_clsobj = myclass(foo=1, bar=2) # mutable
my_namedt = mynamedt(foo=1, bar=2) # immutable
my_list[0] # 1
my_tuple[0] # 1
my_clsobj.foo # 1
my_namedt.foo # 1Behavior is so well-defined in a functional system that it's intensely frustrating dealing with any imperative/OO language.
https://github.com/guigrpa/timm
The onus is on the developer to actually ensure immutability, but as long you're always mutating via the library functions, you should be fine.
I've used more serious solutions, like Immutable.js, but I prefer this instead. The wrapper functions in Immutable.js, in my experience, aren't worth the trouble.
The benchmarks in the Timm readme are interesting though. I would like to see a benchmark of seamless-immutable in production mode added to compare.
The (potential) problem is that it is still possible to mutate objects without the use of timm's utility functions.
If you value the performance benefits (no penalty for freezing objects since timm doesn't do that), and understand that to get the benefits, you must only mutate objects using timm, choose timm.
If you value the additional safety other libraries provide, then absolutely use one of those. It all depends on the use case.
You don't need to freeze objects if you are doing it properly. You could even do static analysis to know (by checking to see if you're reassigning object properties, etc).
Just a quick question is there a helper library for connecting immutable redux-store to localStorage? That is something I dearly miss if there is none although you could write one yourself I guess.
There is also no chaining of method calls or withMutations equivalent. Granted for small stuff not that important but it would be nice to have them in toolbox if use case arises.
But thanks. Immutability in JS is such a pain which will hopefully change in the future. Until then I'm looking for the next best thing.
It also doesn't play nicely with Flow unless you use the static syntax.
I wondered if you might have something to say on performance issues. You didn't encounter anything of that sort?
As a quick summary:
- It doesn't magically improve perf the way some people think it will. It does make easier to use the standard shallow equality implementation of `shouldComponentUpdate`, and can be faster at copying/updating very large objects. However, many people use `toJS()` to extract values, and that is absolutely bad for perf (especially if you call it in `mapState`).
- You either have to use its API everywhere in your codebase, or spend a lot of time encapsulating it to keep your components unaware of its existence.
- It can be harder to debug the contents of the objects.
I'm not sure about specific downsides for using Seamless-Immutable.
My React/Redux links list has a section of articles that discuss the pros and cons of using Immutable.js for performance [1]. I've also got a section on use of immutable data in general [2], including some articles that talk about how to actually use Immutable.js in your app, and articles on using plain JS data immutably.
[0] https://www.reddit.com/r/javascript/comments/4rcqpx/dan_abra...
[1] https://github.com/markerikson/react-redux-links/blob/master...
[2] https://github.com/markerikson/react-redux-links/blob/master...
As I said, I'm not aware of specific downsides to using Seamless-Immutable. It seems like a good balance of enforced immutability in development via freezing and replacement of mutation methods, and compatibility with standard JS object/array notation and usage.
If you manage to avoid all performance pitfalls and can accept some degree of loss in dev productivity, then the performance benefits from structural sharing and laziness can still be worthwhile for a sufficiently data-heavy app. For most other cases it's a very costly premature optimization though.
The Redux docs actually has a very thorough document on the pros and cons of using persistent data structure libraries with Redux: http://redux.js.org/docs/recipes/UsingImmutableJS.html
I'm still hoping for native JS persistent data structures to make it into the ECMAScript spec at some point in the future. The only thing I've found so far is this: https://github.com/sebmarkbage/ecmascript-immutable-data-str...
And I'm not sure if it has gained much traction.
Until native JS persistent data structures get implemented and gain widespread support, I think something like seamless-immutable might be a reasonable compromise.
I think a light wrapper that:
1. Integrated into flow and typescript.
2. Rewrote mutable methods like push/splice as immutable.
... would be sufficient for many needs. I love Immutable, but it does incur a performance penalty for the expressiveness (as far as I know), and works best when starting a new project over integrating into existing (where to/from array becomes more of an issue).
Tcomb does this, and is far and away one of my favorite JS libraries for all of the other features it provides as a result.
Edit: to be clear, tcomb provides runtime types, algebraic data concepts, pattern matching, validation, and with plugins react form generation and other things. Immutability is a simple consequence of needing to guarantee a valid value stays valid; it's not so much a "feature" per se
Immutable({a: 1}) !== Immutable({a: 1})
There is no built in equality function either [1] so that seems like that's a big advantage Immutable.js has over this. Maybe seamless-immutable-diff [2] could be a way to do it but I am not sure on the performance.[1]: https://github.com/rtfeldman/seamless-immutable/issues/186 [2]: https://www.npmjs.com/package/seamless-immutable-diff
Now, a reference comparison only tells you if the objects themselves are different. It's entirely possible to have two different objects with the same or equivalent contents, such as in @kasbah's example. Both objects have a key named `a` that has a value of 1, but the objects are different references. The overall assumption when you update data immutably and compare like this is that _if_ two objects are different references, then they _probably_ have different contents, and it's time to re-render your UI.
- Clarity of code. You can't accidentally mutate data that will then be used elsewhere.
- Performance. Being able to avoid deep equality checks makes a huge difference for React in particular.
One of the reasons immutable JS data structures have been gaining in popularity the last few years is the adoption of React; the virtual DOM diffing algorithm relies on a few state management methods for knowing which nodes need redrawing and the DOM won't update correctly if you directly mutate the data structures the components are using for their state. As for server-side, I can only assume it's the general trend of JS developers increasingly adopting a more functional style. One of the nice side effects (hah) of using immutables the ability to easily keep a history of state transitions and revert to a previous state if something goes wrong. This can be particularly useful when dealing with distributed systems. It's also much easier to reproduce an undesired state when debugging production issues. It's not a silver bullet but it reduces the number of heisenbugs that turn up in production.
You can mutate a component's state and then call `setState()`, and React will still update the component as you'd generally expect. For React, immutability primarily relates to implementing `shouldComponentUpdate` using the standard shallow equality comparison approach for the best performance, and general use of Functional Programming principles for avoiding side effects. For Redux, immutability matters for ensuring that reducer functions are pure with no side effects, as that is a requirement for time travel debugging and proper updates of Redux-connected components.
So, overall immutability is definitely an encouraged approach in the React/Redux ecosystem, and there's specific places where that immutability is important for desired behavior.
For example, imagine the naive example where you just have a global `const store = { ... }` and all components read from it and update it. You could tell everything to re-render on any change, but that's slow. So you can then implement `shouldComponentUpdate` for each component to manually check if their dependent data changed. But that may be a lot of work where each component needs to know quite a bit about the data.
For instance, how do you check if two arrays are the same and without walking both arrays? Depending on your data, it may be as simple as comparing `item.id` of the first item in each array. But it might not be.
On the other hand, you can tell if two immutable datastructures are equal with reference equality (obj1 === obj2). If all of your components take immutable data, then you can abstract away `shouldComponentUpdate` with a reference comparison. This is what React's PureComponent does.
That's the main use-case. Not as many obvious use-cases on the server side in Javascript.