top | item 8541496

Immutable.js – Immutable Data Collections

182 points| swah | 11 years ago |facebook.github.io | reply

85 comments

order
[+] ScottBurson|11 years ago|reply
> The difference for the immutable collections is that methods which would mutate the collection, like 'push', 'set', 'unshift' or 'splice' instead return a new immutable collection.

I think this is an unfortunate design decision which should be reconsidered. Functional operations should have different names than side-effecting operations. In general, I think that while side-effecting operations are commonly verbs, functional operations should be nouns or prepositions.

Particularly in a language without static types, you want to be able to look at an unfamiliar piece of code and see pretty quickly what types it is using. The semantics of mutable and functional collections are similar enough that using the same names is going to be very confusing, particularly in code that uses both kinds of collection -- and such code will definitely exist.

It's important that the names convey the semantics of the operation. Java's 'BigInteger' is a good example of this being done wrong -- the addition operation is called 'add', for example, and I have read that some newbies call it without assigning the return value to something, expecting it to be a side-effecting operation. I think that if it were called 'plus', such an error would be much less likely. We're used to thinking of "a + b" as an expression that returns a value, rather than an object with state.

I understand that introducing new names has a cost: people have to learn them. But keeping the old names is going to drive users nuts. If you won't change the names altogether, at least append an "F" to them or something.

EDITED to add: if you want some ideas, check out FSet: http://www.ergy.com/FSet.html

[+] leebyron|11 years ago|reply
This is great feedback and a decision that I didn't take lightly.

I ultimately decided that the mental cost of remembering a new API would outweigh the potential for accidental return value mis-management.

It's hard to make a decision like this sans-data, so I had to make a gut call. I'm really interested to hear feedback of issues encountered in-practice due to this. Of course, if I'm wrong about this (and there's always a reasonable chance I am!) then I would seriously consider changing the method names in a future major version.

[+] baddox|11 years ago|reply
I empathize with your argument that operations with significantly different behavior ought to have different names, although I think the convenience of using familiar method names might outweigh the confusion of new readers (depending on the size/scope/structure of your software project).

I'm less a fan of your argument that method names like "push" or "add" inherently imply that they mutate their caller. I see no reason for that to be the case, other than in the context of your first argument. I think that an "add" method that returns a new integer without mutating its caller accurately conveys the semantics of the operation just as much as an "add" method that mutates its caller.

[+] mcav|11 years ago|reply
That's silly. All of the methods are side-effect-free. It'd be useless and confusing to suffix some methods and not others.
[+] possibilistic|11 years ago|reply
I wholeheartedly agree. Methods associated with mutation should raise exceptions. If errors aren't eagerly generated, assumptions about behaviour might be made. Not to mention this will make testing harder.

Readability is impacted as well. Building a mutable copy from an immutable should be explicit. These calls should have a very prominent call signature that is easily read, not skimmed over.

Very cool library, but I can sense lots of confusion and debugging resulting from accidental misuse.

Guava's immutables are pretty solid and serve as a great reference.

[+] Too|11 years ago|reply
Good point. One solution to this is loud warnings in the IDE about "unused return value". But since this is javascript it could in many cases be hard for the IDE to know what type you are calling the method on. (And technically all functions in javascript return something, undefined, which by some people might be considered a feature)

Depending on other API designs these warnings could also drown in false positives because some functions BOTH mutate the object and return it and you very rarely store the return value somewhere else, it's just there "for convenience" when chaining calls, such as foobar.add(123).multiply(456).subtract(789). Worse offenders are those that just randomly return something for the sake of it, take memcpy/memmove/etc in C for example, it accepts destination as a parameter and it also returns the very same destination for no good reason, i have never found a reason to use that return value.

An api should be designed so that unused return values in the majority of cases can be considered an error, unless explicitly ignored by casting it to (void) or something, most cases of unused return values you find are just people that are too lazy to check return codes which is about as smart as wrapping every statement in try/catch with an empty catch block.

[+] ninjakeyboard|11 years ago|reply
Not sure if I agree - it's more natural to have only immutable collections if taking a functional approach. Eg see scala or haskell. Does strong typing change that? It's very clear if you're whole code base has no side effects what is happening when and where.
[+] apendleton|11 years ago|reply
Agreed. Related: "Methods which return new arrays like slice or concat instead return new immutable collections" seems odd to me, at least for slice. Isn't one of the advantages of immutable arrays that you can operate on slices without copying because of the guarantee that the underlying representation won't change?
[+] swannodette|11 years ago|reply
It's great to see more implementations of persistent data structures. I think over time people will begin to see that they allow us to radically rethink how we organize very complex programs that we have long believed are better constructed on stateful objects - user interfaces. It's been thrilling to see people discard many long held assumptions - the ClojureScript community has been running with UIs constructed on persistent data structure via Om (a layer over React) to startling effect - I highly recommend watching this recent demo from CircleCI https://m.youtube.com/watch?v=5yHFTN-_mOo.
[+] amelius|11 years ago|reply
Cool video. But I don't get it really :S I just skimmed through the documentation and it says:

> In return for this effort, we get powerful debugging tools

Is this the primary purpose of this tool? Or is it the undo feature?

[+] gcanti|11 years ago|reply
Om is an amazing and inspiring project! thanks for writing it.
[+] skrebbel|11 years ago|reply
I've been using this (v2, though) rather extensively the last few months so I figured I'd share some experiences.

First off, it works great. The TypeScript definition file also works fine as an API documentation of sorts, although it would be nice if some web site with a clickable TOC could be generated from it.

I was a little confused at the beginning how the "Sequence" base class (changed name to Iterable in v3) was an arbitrary key=>value mapping (with no apparent relation between the keys and the ordering of the key/value pairs) that I had expected would be called "Map". The name change made this clearer for me: it's basically an abstract interface, not a concrete datastructure.

The TypeScript support, to my experience, does not work as advertised on the tin, especially when you nest data structures. For example, code like this compiles fine:

   var q = Immutable.Map<string, Immutable.List<number>>();
   q.set("hello", Immutable.List([1,2,3])); // typechecks until here
   q.updateIn(["hello", "wrong"], false); // shouldn't be possible, but is.
Similarly, if you use Immutable.Record to build immutable objects, then you can still call set() with any key because the key is a string instead of something TypeScript could typecheck. Meanwhile, using direct property access on Immutable.Records, like this, will fail:

    var Banana = Immutable.Record({length: 0, colour: "yellow"});
    var someBanana = new Banana();
    someBanana.curve = 5.0;         // should fail, but doesn't
    console.log(someBanana.length); // fails, but shouldn't
The last line will fail to compile in TypeScript even though it works in Immutable.js. I managed to write a wrapper for Immutable.Record() that "solves" this, but at the cost of highly verbose repetition for each record class.

I tried very hard to "fix" this, and I have become aware that this is nearly entirely due to limitations of TypeScript's very poor generics support. It really couldn't be any better than it is. But still, the front page claims great TypeScript support and I'd say it's half-assed at best.

I chose Immutable.js over Mori because I think the API is slightly less verbose and more JavaScript-ish. I think this is mostly a matter of taste.

I found that I kept forgetting the names of methods, or being unsure about their exact signatures. Also, I often use plain JS datastructures in "little" parts of the code (stuff that doesn't persist cross function calls), I use LoDash and ES5 built-ins to manipulate those, and these functions all have subtly different names and semantics again. I suspect that very few codebases won't do this in practice: mix Immutable.js data structures with native JS data structures all over the place, and a need to manipulate both here and there.

I think transducers may be an opportunity there. Our very own jlongster has a nice writeup [1] of how they enable you to use the exact same data manipulation syntax for both native JS data structures and Immutable.js collections at no performance cost. I think that maybe Immutable.js should embrace this and release an alternative build of the library that has absolutely no manipulation functions except the hooks that transducers need to consume and produce Immutable.js collections.

Finally, I've been doubting whether Immutable.Record is worth the hassle. Maybe a single function that can easily clone a JS object but change 1 field is more than good enough. For example, with lodash/underscore this function would be a oneliner like:

    var newRecord = _.merge({}, oldRecord, {someKey: newValue});
(there might be a "shorter" way, I find the underscore API horribly obtuse and difficult to figure out, but that's not the point here)

The biggest disadvantage of using vanilla JS objects over Immutable.Record is that you're not sure that it'll not mutate if you forget to Object.freeze. I doubt many record-type objects have so many fields that cloning them has a larger performance overhead than Immutable.js's internals. I'm curious what other people's ideas and experiences are here.

[1] http://jlongster.com/Transducers.js--A-JavaScript-Library-fo...

[+] leebyron|11 years ago|reply
I'm not happy with the Records impl yet. This is great feedback and resonates with some things I've already been thinking about. I'm especially not happy about how Records play with Typescript at the moment.

Also, please keep feedback coming about Typescript. I'm sorry you feel it's half-assed, I'm trying to make the best of what Typescript gives me. You might notice that the .d.ts file is full of comments where a more expressive type system would allow more accurate type information.

If you have concrete suggestions for improving the TypeScript definition file, please please hop over to github.com/facebook/immutable-js/issues and write them up.

[+] ebabchick|11 years ago|reply
Random observation -- what's up with almost 20 of the 24 'contributors' to this project mostly having made 1 edit changes to the README? Is this some kind of pervasive Github resume padding scheme that I'm just now picking up on? (it will show the repo in the "Repositories contributed to" section of your profile even for just those 1-line README edits)

https://github.com/facebook/immutable-js/graphs/contributors

[+] spicyj|11 years ago|reply
As the supposed #2 contributor to the project (with 3 commits), I'd say it's far more likely that the README is simply the most visible piece of the project (or was, before the website was released today) and people contribute back typo fixes as they encounter errors.
[+] jonahx|11 years ago|reply
It's possible, but it's also standard practice on github to submit even minor typos or spelling errors via a pull request, and that automatically makes you a contributor if accepted. So there's not necessarily anything nefarious going on.
[+] leebyron|11 years ago|reply
Not nefarious, but definitely something to keep in mind if you recruit via github committer lists :)

I accept any reasonable pull request, even if it's a spelling fix. There have been some great bug fix pulls as well.

[+] etrinh|11 years ago|reply
I've been using this library for a project, and here are my thoughts on it:

1. Immutable data structures are a huge win. I can't count the number of times I've been bitten by a bug caused by some subtle mutation that happened in a part of my code that I wasn't expecting.

2. Using this library for data structures everywhere, as a replacement for native JS arrays and objects, requires you to have discipline in naming your variables so that you can infer their types. Things can get pretty frustrating when you try to map over a Set, thinking it's a Map, for example.

3. The most annoying thing might be the documentation, which consists of a type definitions file and liberal comments. It's ok, but hardly a great interface for exploring the API.

Overall, liking the library so far. I think with good, searchable docs (with examples of API usage) this could be something really great.

[+] yawaramin|11 years ago|reply
Apparently they support TypeScript, which would give you types without the pain of a naming scheme....
[+] raspasov|11 years ago|reply
There's also this library by David Nolen which seems similar https://github.com/swannodette/mori . Haven't used either library; someone with more experience might want to chime in on the differences/similarities between them.
[+] Havvy|11 years ago|reply
So, how does this compare to Clojurescript's Mori?
[+] leebyron|11 years ago|reply
Performance: comparable Data-structure techniques: nearly the same API: do you like point-free functions (mori) or methods (immutable.js)

mori is a direct compile of clojurescript's excellent data structures and functional tools (written in clojurescript, of course) to javascript. It favors a clojure-style API.

immutable.js is entirely written in JavaScript and favors Idiomatic JS style API.

[+] zbyte64|11 years ago|reply
I have started using this with our React.js project and have been able to squeeze out efficient DOM updates comparable to Om.

For fun I also made a library that overloads Immutable.js and JSON operations (and can do the same for mori): https://github.com/zbyte64/dunderdash

[+] jonahx|11 years ago|reply
Doesn't React already do efficient DOM updates for you with their virtual DOM and it's diffing mechanism? Or are you referring to something else?
[+] malandrew|11 years ago|reply
What are the implications on memory usage in a long running app with something like immutable.js?

For example, let's say we have a mercury app [0], and the state of the app is based on a persistent immutable data type. It would seem to mean that so long as the state of the app doesn't change much that everything would work just fine because there total number of diffs is minimal. However, what happens in the case of an app where there are event sources that produce lots of data (mouse movements for example) and therefore can result in lots of diffs. Wouldn't this be a source of memory usage that just keeps creeping up and for which memory is never released?

[0] https://github.com/Raynos/mercury

[+] leebyron|11 years ago|reply
There should not be linearly increasing memory usage. If you find memory leaks like this, please file bugs on github issues. AFAIK there currently aren't any.

Of course, if you do something like keep an undo stack around (check out swannodette's blog for a great example of this) then of course you have linearly increasing memory usage. One great property of persistent data structures is that the memory usage will be much smaller than if you kept your undo stack as copied JS values because of structural sharing (see one of swannodette's recent talks for a great explanation of this).

In practice, if you're implementing an undo stack, you should remain conscious of memory usage. You may want to only keep a fixed maximum number of previous states around so you don't have linearly increasing memory use.

[+] infinity0|11 years ago|reply
Hi! We would dearly love to use this in our code (a MEGA webclient subproject) but it's 51KB! Do you have ways to build only parts of this so that I can include (e.g.) only Immutable.Set? At the moment I'm using a custom immutable wrapper around a ES6 Set polyfill.
[+] leebyron|11 years ago|reply
Yes, please open a github issue about this. There are some non trivial changes needed to make this possible, but this is something I would love to enable in a future version.

Also, if you're minifying and gzipping your static resources (standard practice these days), then Immutable.js is a relatively fit 15KB. For comparison, lodash is 8KB and jQuery is 30KB.

I definitely understand the desire to not include code that will never run (we pain to optimize this at FB), but at least it won't break the proverbial bandwidth bank.

[+] gcanti|11 years ago|reply
What do you think about vanilla `Object.freeze`? Any drawbacks? Here my considerations at the moment:

Pros:

- you can use native data structures instead of continually wrap / unwrap

- you can use native methods on them (map, filter, ...)

- hence (almost) no interoperability problems with other libraries

- overall your coding style doesn't change so much

- (possible perf gains since the js engine knows the object is frozen? - I really don't know)

- EDIT: 0KB dependency

Cons:

- you must find / write a way to update efficiently

[+] swannodette|11 years ago|reply
Lack of update is a show stopper. In general I would expect Immutable JS to trounce anything via Object.freeze with respect to performance. Real hash maps and sets are useful to organize programs - the semantics of Object.freeze on ES6 Map and Set are unclear and still suffer from above problems. Again Immutable JS has a big advantage.

In the context of building real applications with immutable data structures and a library like React that makes them easy to integrate - many of your concerns don't apply at all.

[+] Too|11 years ago|reply
Last time i checked Object.freeze actually made things slower, alot slower. Sounds counter intuitive and it might eventually change but that's the current state in most popular browsers.
[+] rpwverheij|11 years ago|reply
"Subscribing to data events throughout your application [..] creates a huge overhead"

So could I do without event listeners when using Immutable data? Say I have a view listening to data changes with event listeners... could I notify it about changes in data without listeners if I use immutable data?

[+] saurabhnanda|11 years ago|reply
Is it possible to use this as a drop-in replacement for Angular POJOs that hold $scope data? We're hitting a whole class of bugs which are basically due to mutability of native JS objects, and we could use something like this for the most complex ng-controllers.
[+] leebyron|11 years ago|reply
I've never tried this myself, but anything is possible.

If you get this working, and want to write a blog post about it, let me know!

[+] franciscop|11 years ago|reply
I think I'm getting the example wrong. Wouldn't this:

    var map = Immutable.Map({a:1, b:2, c:3});
    map = map.set('b', 20);
    map.get('b'); // 20
Give 2 instead of 20, since b is inmutable?
[+] rtfeldman|11 years ago|reply
Only the map variable points to an immutable object. The second line reassigns that variable to a new (also immutable) Map instance, which is an exact copy of the original except that b has been changed.

Does that make sense?

[+] rpwverheij|11 years ago|reply
to put what rtfeldman said in code:

    var map = Immutable.Map({a:1, b:2, c:3});
    var map2 = map.set('b', 20);
    map.get('b'); // 2
    map2.get('b'); // 20
[+] amelius|11 years ago|reply
> Immutable always returns itself when a mutation results in an identical collection, allowing for using === equality to determine if something has changed.

But any smart equality-testing function would already include this.

[+] nawitus|11 years ago|reply
Was this really hosted on Facebook initially? I remember this library before May, which is the date of the initial commit.
[+] jypie|11 years ago|reply
What is the benefit of having immutable variables that are just fake-built at runtime? At compile time, knowing some variables are constant and will not change would give the interpreter/compiler a lot of optimization chances but does this apply the same at runtime as well?

the website says "efficiency" because immutable variables wouldn't need to be deep copied but would the deep copy operations be that frequent in JS?