New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to modify an immutable array? #43

Closed
holyjak opened this Issue Jun 5, 2015 · 13 comments

Comments

Projects
None yet
7 participants
@holyjak
Contributor

holyjak commented Jun 5, 2015

When using seamless-immutable, what is the recommended way of deriving new values from existing one (when map and merge aren't enough)? Typically, immutable data structures (IDS) have functions that create new IDS derived from them, such as Immutable.js' aList.push(1, 2) or Clojure's updateIn, assocIn, dissoc, etc. In seamless-immutable all array/object mutating functions just throw an error so to create a new IDS I suppose I have to do something like

var newArray; var tmp = Immutable([1,2,3]).asMutable(); tmp.push(4); newArray = Immutable(tmp);

Is that correct? Thank you!

@rtfeldman

This comment has been minimized.

Owner

rtfeldman commented Jun 5, 2015

For that example, I would just use concat:

 // equivalent to Immutable([1, 2, 3, 4])
Immutable([1,2,3]).concat([4])

The Object and Array methods that do not mutate are still available, although they return Immutable instances instead of normal arrays and objects.

The non-mutating array methods seamless-immutable supports are map, filter, slice, concat, reduce, and reduceRight, and for objects just keys.

Does that help?

@RangerMauve

This comment has been minimized.

RangerMauve commented Jun 5, 2015

Just to interject, the docs say that the arguments to concat don't have to be an array, so the square brackets can be left out, and it'll look more like your push code.

@holyjak

This comment has been minimized.

Contributor

holyjak commented Jun 5, 2015

Thanks. I need to think through it to see whether filter, concat etc. cover
all modification cases. I guess eg. replacing an element at a given index
could be achieved with map though that is cumbersome.
5. juni 2015 19:30 skrev "Richard Feldman" notifications@github.com:

For that example, I would just use concat:

// equivalent to Immutable([1, 2, 3, 4])
Immutable([1,2,3]).concat([4])

The Object and Array methods that do not mutate are still available,
although they return Immutable instances instead of normal arrays and
objects.

The non-mutating array methods seamless-immutable supports are map, filter,
slice, concat, reduce, and reduceRight, and for objects just keys.

Does that help?


Reply to this email directly or view it on GitHub
#43 (comment)
.

@rtfeldman

This comment has been minimized.

Owner

rtfeldman commented Jun 5, 2015

Keep in mind that the function map accepts does receive index, so you can just do:

var indexToUpdate = 2, newVal = "THREE";

Immutable(["one", "two", "three", "four"]).map(function(val, index) {
  return (index === indexToUpdate) ? newVal : val;
});

Admittedly more verbose than mutableArray[indexToUpdate] = newVal, but in my experience this hasn't come up all that often.

@holyjak

This comment has been minimized.

Contributor

holyjak commented Jun 8, 2015

So if I should sum it up, seamless-immutable supports most/all mutation cases though sometimes you need to think carefully how to combine the available methods to achieve it.

My particular use case is: If an array has an element with a given id, remove it and re-insert it to the end with an increased count; otherwise just append it. So I'd need to do something like:

var _ = require("lodash");
var log = Immutable([]);
function add(idToFind) {
var foundIdx = _.find(log, { id: idToFind });
var count = 1;
if (foundIdx) {
  log = log.slice(0, foundIdx).concat(log.slice(foundIdx+1));
  // With icepick I could do: `log = i.splice(log, foundIdx, 1);`
  count += 1;
}
log = log.concat({id: idToFind, count: count});
} //fn

In some cases, when performance is important - e.g. when I need to "change" an element of a long array relatively often - then I have to use a less straightforward solution. For example:

// This is slow as it iterates over the whole array each time, not stopping after the element is found:
log = log.map(function(val, idx) { if(val.id === idToFind) return {id: val.id, count: val.count+1}; else return val; });

// This is *hopefully* faster since it only needs to iterate on average over 1/2 of the array
// (even though we still need to combine a few operations):
var foundIdx = _.find(log, { id: idToFind });
var val = log[idToFind]; var newval = {id: val.id, count: val.count+1};
log = log.slice(0, foundIdx).concat(newval).concat(log.slice(foundIdx+1))

On the other hand it might me better to just use mutable data structures for this and only use immutability where changes are infrequent.

@rtfeldman

This comment has been minimized.

Owner

rtfeldman commented Jun 8, 2015

Yeah, it all depends on your use case...for our use case, we haven't encountered any performance problems where the underlying culprit turned out to be using immutable data structures over mutable ones. Of course, you may be dealing with much larger data than we do. 😄

@bitinn

This comment has been minimized.

bitinn commented Jun 9, 2015

Given the lack of object mutation method, am I right to say the common (and fastest) way to update (or diff) immutable object is to use merge/without? I have a feeling using asMutable then mutate object and then wrap them into an immutable again will be at least O(n).

PS: assume I want to modify a nested immutable object { a: { b: 1 } }, what's the best way to set b = 2 in seamless-immutable case?

@rtfeldman

This comment has been minimized.

Owner

rtfeldman commented Jun 9, 2015

Yeah, merge and without are definitely the most common ways.

For the nested case, just use merge to override a to have a new value that includes a new value of b. If you really needed to, you could get fancier with a custom merger, but I've personally had no issues just using vanilla merge.

@holyjak holyjak closed this Jul 10, 2015

@dgieselaar

This comment has been minimized.

dgieselaar commented Sep 23, 2015

@rtfeldman: there's a pattern w/ gulp-cached and gulp-remember which looks a little like this:

stream
  .pipe(cache())
  .pipe(doStuffOnUncachedItemsOnly())
  .pipe(remember())
  .pipe(doStuffOnAllItems())

Perhaps something similar would would solve this problem as well:

collection
  .map(mapFn)
  .only(filterFn)
    .map(mapFilteredFn)
  .all()

Just thinking out loud here.

@bradwestfall

This comment has been minimized.

bradwestfall commented Apr 8, 2016

I know it's an old thread, buy why does .concat not work after the fact

let array = Immutable([1,2,3]).concat([4])
array.concat([5])
console.log(array) // [1,2,3,4]
@RangerMauve

This comment has been minimized.

RangerMauve commented Apr 8, 2016

@bradwestfall array is immutable. .concat() always returns a new array. Your example should instead be:

let array = Immutable([1,2,3]).concat([4])
let array2 = array.concat([5])
console.log(array2) // [1,2,3,4,5]
@bradwestfall

This comment has been minimized.

bradwestfall commented Apr 8, 2016

oh, duh. Thanks

@starInEcust

This comment has been minimized.

starInEcust commented Apr 11, 2017

How about splice?

// pretty ugly
log.slice(0, foundIdx).concat(newval).concat(log.slice(foundIdx+1))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment