> 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.
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.
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.
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.
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.
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.
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?
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.
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.
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.
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)
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.
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.
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.
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.
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.
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?
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.
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.
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.
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.
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.
"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?
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.
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.
> 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.
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?
[+] [-] ScottBurson|11 years ago|reply
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
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'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
[+] [-] possibilistic|11 years ago|reply
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
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
[+] [-] apendleton|11 years ago|reply
[+] [-] swannodette|11 years ago|reply
[+] [-] amelius|11 years ago|reply
> 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
[+] [-] skrebbel|11 years ago|reply
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:
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: 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:
(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
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.
[+] [-] brentburgoyne|11 years ago|reply
http://brentburg.github.io/immutable-docs/api/
[+] [-] ebabchick|11 years ago|reply
https://github.com/facebook/immutable-js/graphs/contributors
[+] [-] spicyj|11 years ago|reply
[+] [-] jonahx|11 years ago|reply
[+] [-] leebyron|11 years ago|reply
I accept any reasonable pull request, even if it's a spelling fix. There have been some great bug fix pulls as well.
[+] [-] RoboTeddy|11 years ago|reply
[+] [-] etrinh|11 years ago|reply
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
[+] [-] raspasov|11 years ago|reply
[+] [-] Havvy|11 years ago|reply
[+] [-] leebyron|11 years ago|reply
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
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
[+] [-] malandrew|11 years ago|reply
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
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
[+] [-] leebyron|11 years ago|reply
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
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
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
[+] [-] rpwverheij|11 years ago|reply
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
[+] [-] likeclockwork|11 years ago|reply
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
[+] [-] leebyron|11 years ago|reply
If you get this working, and want to write a blog post about it, let me know!
[+] [-] franciscop|11 years ago|reply
[+] [-] rtfeldman|11 years ago|reply
Does that make sense?
[+] [-] rpwverheij|11 years ago|reply
[+] [-] amelius|11 years ago|reply
But any smart equality-testing function would already include this.
[+] [-] nawitus|11 years ago|reply
[+] [-] jypie|11 years ago|reply
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?
[+] [-] oahziur|11 years ago|reply
[+] [-] adam23|11 years ago|reply