Hacker News new | past | comments | ask | show | jobs | submit login
Immutable Data Structures That Are Compatible with Normal JS Arrays and Objects (github.com/rtfeldman)
121 points by kasbah on Aug 12, 2017 | hide | past | favorite | 39 comments



There's generally three categories of immutable data libraries for JS:

- 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...


I've build a library Transmutable which falls into the third category :)

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.


This is a neat idea, hadn't thought of this but it could definitely help me bring new developers into the immutable style more easily.

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?


As far as I know, proxies simply will not work in environments that do not support ES6, because they require purpose-built JS engine support (see some comments at [0] as examples).

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...


on the beginning I used getters/setters but I switched to Proxy because it allowed for simpler implementation and more flexibility.

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


Nice, I hadn't seen that yet. I'll have to take a look at it later.


There is a huge loss in usefulness for immutable objects when immutability is not a default and you have that many different implementations.

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.


I totally agree with you. I wish we had a built-in immutable array type and a built-in immutable object type. I love the way it's done in Python, where you have Lists vs Tuples and Class Objects vs NamedTuples. You can consume them the same way:

    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 # 1


More generally, this is why I don't find languages that add "functional" features very compelling.

Behavior is so well-defined in a functional system that it's intensely frustrating dealing with any imperative/OO language.


I've been using this:

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.


Well if the onus is on the developer it kind of defeats the purpose no? As you likely know seemless-immutable also has a "production" mode that has better performance but doesn't ensure it. That seems like a good compromise to me, so that at least during development you get errors when things are being mutated.

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.


It doesn't defeat the purpose. It only mutates if there is actually a change to the data structure. When used properly, timm works great.

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).


Nice! I will try it out as soon as I can but if it rids me of Immutable.js I will be so happy. Its API is so convoluted and you have to use .toJS() all the time since using get() is so clunky and while transforming value back to JS it will fail if it was undefined. Many small things that succeed in killing the joy of immutability for me. (But mostly the huge API that requires way too much studying)

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.


I've been using this library for over a year, I'd say if you have an existing project that you'd like to use Immutable objects in then it's a great choice with minimal downsides. For a greenfield project Immutable.js is probably a better bet as the newer data structures come with a lot of benefits.


With something like ImmutableJS or Mori, or any other persistent data structure library that doesn't use native JS objects, there's just a lot of general friction during development that can seriously slow down both developer productivity (dealing with interop, losing access to object destructuring, spread, being limited to data access/mutation APIs provided by the libraries, difficulty in debugging, etc) and actual app performance (converting to or from JS is often expensive and can completely negate the perf benefits of the persistent data structures when overused in the wrong places, and it's extremely easy to overuse it in the wrong places if you're not familiar with React/Redux's inner workings).

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 have just started a React-Redux project using Immutable.js and am considering switching it over to this as I can see a lot of advantages in the compatibility with JS data. What downsides do you think I would encounter after switching?


I wrote a long Reddit comment a while back that describes the reasons why I advise that people _not_ use Immutable.js [0].

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...


You don't appear to be actually responding to my question but are giving unsolicited advice and plugging your projects :/


I actually misread your question a bit at first - I originally thought you were asking about switching _to_ Immutable.js, not away from it. I realized that before I posted it, and decided my answer was still relevant to the aspect of "why someone might want to switch away from Immutable.js". So, in that sense it's adding context to the reasoning behind your question.

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.


The main thing I can think of is that it will add all it's internal methods to your objects, which restricts which properties you can use – eg, "update", "merge" etc. This isn't usually an issue but we happened to have some policy objects with the same property names.

It also doesn't play nicely with Flow unless you use the static syntax.


Ah, wasn't aware. Good to know. Well, I never did get Immutable.js to play nicely with Flow which is kind of why I gave up on Flow.

I wondered if you might have something to say on performance issues. You didn't encounter anything of that sort?


Performance was the main reason we switched over. We found using Immutable objects to be the key to a performant UI when using something like React that's diffing your props every render loop.


So you would have your props be Immutable.js objects? I wasn't sure if doing that would be a good idea but sounds like that has worked out for you.


Modern JavaScript is full of syntax sugar but lacks operator overloading which would be really useful for custom data structures. This surprising me.


Does `Object.freeze` still incur a performance penalty? Static enforcement of immutability is possible today for array types using something like `$ReadOnlyArray` tagging in flow, if a bit clunky.

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).


Most libraries have flags for an environment variable to skip freezing in "production" mode; any decent minifier will remove the conditions since they can't be reached, so there's no performance penalty.

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


This looks great. Do you know the exact reason why ie10/11 is not supported?


I believe it actually supports IE9+ but the tests are currently failing so they are marked red in the test array. I suspect that they are only failing for the development branch and everything works with IE9+ if you use the latest release.


It requires ES5 features like Object.freeze and Object.defineProperty to work correctly.

https://github.com/es-shims/es5-shim#may-fail


Is there perf gain when comparing 2 immutable obj deeply with === ?


A `===` comparison is explicitly _not_ a deep comparison. It's a reference comparison, ie, "are these two variables pointing to the same object in memory?". Under the hood, that probably is interpreted as a simple pointer comparison, which is fast and cheap. That's why it's the preferred approach for doing comparisons in cases like this. _If_ you update your data immutably, then you get the benefit of quick and simple comparisons to see if things are different.

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.


It appears equality checks for objects is not supported i.e.:

    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


seamless-immutable-diff and seamless-immutable-cursor look like neat add ons for this.

https://github.com/micnews/seamless-immutable-diff

https://github.com/MartinSnyder/seamless-immutable-cursor


Any good primers on why it's beneficial to use immutables in JS? (especially with server side dev)


A hard part of React is determining when and what to re-render. It's one of the main things the state-management tools solve (Redux, MobX, etc).

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.


In general hand-wavy terms, it helps you write side-effect free functions; if you are operating on a data structure defined outside of a function's scope and mutate it directly, you lose reproducibility and your code becomes very difficult to test. By ensuring that the data structures are immutable, you remove one common source of unpredictable behavior. You can be confident that no matter how many times you pass a function a particular state, the resulting state will always be the same. You can do this without immutable data structures by being very careful and knowing exactly which operations mutate and which return new structures (you'll see JS devs using `Object.assign` and `Array.concat` for this purpose), but enforcing immutability with a library takes the burden off the individual developer.

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.


To add a bit more context: React itself doesn't care about mutations in terms of re-rendering and virtual DOM diffing. In fact, it doesn't even care about mutations in terms of calling `setState()`. I'll recap a couple previous comments of mine at https://news.ycombinator.com/item?id=14706862.

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.


Not sure what you mean by primer but I prefer using immutable data structures because it eases programming in a functional style. I use Redux for server-side as well as front-end projects but using Redux without immutable data structures is a PITA because you have to constantly think about whether you are mutating things or not. I have been using immutable.js a lot but the difference from standard JS data types is kind of cumbersome and can become annoying so this project looks really neat. I am looking forward to giving it a try.


Two reasons:

- 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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: