GithubHelp home page GithubHelp logo

rtfeldman / seamless-immutable Goto Github PK

View Code? Open in Web Editor NEW
5.4K 59.0 194.0 562 KB

Immutable data structures for JavaScript which are backwards-compatible with normal JS Arrays and Objects.

License: BSD 3-Clause "New" or "Revised" License

JavaScript 100.00%

seamless-immutable's Issues

case

Very cool! I think it's frowned upon though to have functions that start with a capital letter that are not constructor functions.

Question: How does this compare to icepick?

Hello,

I wonder whether I should pick seamless-immutable or icepick. They both seem to be doing the same thing, providing utilities for "modifying" deeply frozen data structures. I can see some differences but know too little to be able to really compare them and decide. Could you be so kind and describe the advantages of seamless over icepick and vice versa? Thanks a lot!

PS: I'll ask at the other project as well

Using Map method with React causes infinite recursion

I am not 100% sure what's happening as my research was limited. Still I'll try to explain the problem.

var constants = immutable({
        BASE_PATH: '/my/files'
    }),
    BASE = constants.BASE_PATH,
    menu =  [
        {
            link: BASE,
            title: 'Files'
        },
        {
            link: `${BASE}/:sharing`,
            title: 'Sharing'
        }
    ];

...

    render() {

        var moreParams = ( this.props.params.splat || '' ).split('/'),
            command = moreParams.length && moreParams[0].charAt(0) == ':' ? moreParams[0].substr(1) : null;

        return (
            <div className="_personal-storage">
                <Menu items={ menu } />
                <Storage base={ BASE } callback={ processCommand }>
                    <h1>Personal storage</h1>
                </Storage>
            </div>
        );
    }

Everything works this way, but as soon as I make menu immutable, it goes to the weirdest infinite recursion in seamless-immutable.js:

Uncaught RangeError: Maximum call stack size exceeded
defineProperty  @   (program):779
addPropertyTo   @   seamless-immutable.js:5
makeImmutableObject @   seamless-immutable.js:342
Immutable   @   seamless-immutable.js:371
Immutable   @   seamless-immutable.js:367
Immutable   @   seamless-immutable.js:367
Immutable   @   seamless-immutable.js:367
makeImmutableArray  @   seamless-immutable.js:106
Immutable   @   seamless-immutable.js:354
Immutable   @   seamless-immutable.js:367

The weirdest part is - it happens only if I pass menu as a property to a react component. If I just log it to the console it works just fine. It doesn't matter if I make it immutable on declaration or later - it works the same.

When I've been debugging it, I couldn't believe my eyes - after making immutable my objects, it started making immutable React stuff {$$typeof: Symbol(react.element), key: null, ref: null, props: Object, _owner: ReactCompositeComponentWrapper…}$$typeof: Symbol(react.element)_owner: ReactCompositeComponentWrapper_self: null_source: null_store: Objectkey: nullprops: Objectref: nulltype: _default(props)__proto__: Object and this is where the infinite recursion happens. But why does it make it immutable at all? I have no idea.

Incompatible with Object.create(null)

Hello. Thanks for seamless. It's a very nice piece of software.

I was surprised however that I cannot create immutable object from an object without prototype (e.g. acquired with Object.create(null):

Immutable(Object.create(null));
// TypeError: target.hasOwnProperty is not a function

It is also impossible to merge such an object into immutable:

var o1 = Immutable({a: 1});
var o2 = Object.create(null);
o2.b = 2;
o1.merge(o2);
// TypeError: other.hasOwnProperty is not a function

Obviously it depends on hasOwnProperty method of the original object, which is usually available in a prototype chain.

Can we use global Object methods instead, like Object.getOwnPropertyDescriptor? I'll be happy to provide PR, but would like to discuss it first.

Circular references throw stack overflows

If you call Immutable(obj) on an obj that contains a circular reference, you get a stack overflow as it tries to clone it.

I don't think supporting objects containing circular references is a good idea, but you should at least get a nice error message.

A heuristic could be useful here for performance, e.g. start by just keep count of how big the stack is getting; if it gets over (let's say) 1,000 frames, start over with circular reference detection.

Immutable.js like Records

I'd like to be able to use this library to approximate Immutable.js Records: https://facebook.github.io/immutable-js/docs/#/Record

The benefits I see from this are helper functions (e.g. calculated fields on a data object- getName() on an Immutable({firsName: ..., lastName: ...}), defaulting fields, and validation.

From #25, I can see you've chosen not to support object prototypes by default, but I was wondering if there was a way to opt into its use. Looking at the code, I couldn't find a simple solution for this.

Do you have suggestions on how this type of functionality could be added, or potential hooks to enable the usecases above (defaulting might be the trickiest, but the others I feel could be handled by methods on the object)?

Thanks!

Is there any interest in allowing mutable methods to return copies?

I've moved some bits around in a local clone of the repo so that rather than throwing an exception when any of the banned mutable methods are called, a quick copy is created and is mutated then returned as immutable.

Most of this logic happens in a new function.

function proxyMutableMethod(obj, methodName) {                                                                                                   
   var methodRef = obj[methodName];                                                                                                               

  addPropertyTo(obj, methodName, function() {                                                                                                    
    var host = obj instanceof Array ? [] : {},                                                                                                   
         copy = quickCopy(obj, host);

    methodRef.apply(copy, arguments);
    return Immutable(copy);
  });
}

Rather than calling banProperty for each of the banned methods, proxyMutableMethod is called instead.

Now aside from the obvious performance implications (all these operations become at least O(n)*) is there a reason why this isn't already a feature of the library? I understand that the performance itself may be enough of a reason, but it makes it more friendly for people coming from Clojure or Immutable.js. Just want to know whether I'm missing something before I crack on with writing the tests and amending the docs so I can send a pull request.

* It wouldn't be too much work to add an interface for defining structural sharing overrides for some of the mutable methods though.

merge doesn't return the same object in the case of nested immutables.

Here's current behavior as of 1.3.0:

obj = Immutable( {a: [1,2,3]} )
obj1 = obj.merge( {a: Immutable([1,2,3]) } )
obj2 = obj.merge( {a: [1,2,3] } )
obj === obj1 //false
obj === obj2 //false

obj3 = Immutable( {a: {b: 4} } )
obj4 = obj3.merge( {a: {b: 4} } )
obj5 = obj3.merge( Immutable({a: {b: 4} }) )
obj3 === obj4 //false
obj3 === obj5 //false

Should functions be treated as already immutable?

Technically JS functions are also mutable objects. Is it worth going through the trouble of converting them to be properly immutable? (e.g. freezing them etc.)

I'm defaulting to "no" for the sake of conserving performance, as functions are typically not used to store data, but noting the decision here in case it merits revisiting later.

merge doesn't return the same object in the case of nested immutables.

Here's current behavior as of 1.3.0:

obj = Immutable( {a: [1,2,3]} )
obj1 = obj.merge( {a: Immutable([1,2,3]) } )
obj2 = obj.merge( {a: [1,2,3] } )
obj === obj1 //false
obj === obj2 //false

obj3 = Immutable( {a: {b: 4} } )
obj4 = obj3.merge( {a: {b: 4} } )
obj5 = obj3.merge( Immutable({a: {b: 4} }) )
obj3 === obj4 //false
obj3 === obj5 //false

update mutable objects in a callback?

Like how immutable-js does it, but maybe it passes a mutable copy into the function?

Like this?

var item = Immutable({name: "bob"})

// new Item is immutable, obj is mutable
var newItem = item.mutate(function(obj) {
   obj.name = "henry"
})

The rationale is that it's really handy to be able to make multiple changes to an object in one go, and get an immutable object back. I'd prefer to have that callback be an immutable function itself, but I can't think of a good way to set properties without breaking type safety (imagine trying to use your .merge with flow).

Merge does a copy if a value is an array

Hi,

Thank you for that librairy, much easier to integrate in a React/Flux stack than immutable.js!

I want to use immutable variables to be able to not refresh my UI if the content of my objects doesn't change. In the shouldComponentUpdate of my components, i want to test the strict equality between the old and the new prop. But it won't work as expected because:

> o = {a: [1, 2]}
{ a: [ 1, 2 ] }
> o2 = o
{ a: [ 1, 2 ] }
> o === o2
true
> i = Immutable(o)
{ a: [ 1, 2 ] }
> i2 = i.merge(o2)
{ a: [ 1, 2 ] }
> i === i2
false

I would like to read true here.

The same test with an integer / string is working "as expected":

> o = {a: 'toto'}
{ a: 'toto' }
> o2 = {a: 'toto'}
{ a: 'toto' }
> i = Immutable(o)
{ a: 'toto' }
> i2 = i.merge(o2)
{ a: 'toto' }
> i === i2
true

My hopes are shattered!

But maybe I am missing something?
Thank you for your inputs.

Add deepEquals

Add a deepEquals to both ImmutableArray and ImmutableObject which skips checking any branches where the children are === and immutable.

Merge ought error out when passed a string.

current behavior:

  foo = Immutable({key: 'value'})
  // Object {key: 'value'}
  foo.merge('whoops')
  // Object {0: "w", 1: "h", 2: "o", 3: "o", 4: "p", 5: "s", key: 'value'}

While this conforms with JavaScript strategy of coercing strings into objects, it leads to crazy errors. FWIW, lodash simply ignores strings that are passed to merge.

README example is mutable

I ran the first code example in a Node 4.1.1 console and everything that should not have mutated did. What could I be missing?

var Immutable = require('seamless-immutable');
var array = Immutable(["totally", "immutable", {hammer: "Can’t Touch This"}]);

array[1] = "I'm going to mutate you!"
array[1] // "I'm going to mutate you!"

array[2].hammer = "hm, surely I can mutate this nested object..."
array[2].hammer // "hm, surely I can mutate this nested object..."

for (var index in array) { console.log(array[index]); }
// "totally"
// "I'm going to mutate you!"
// { hammer: "hm, surely I can mutate this nested object..." }

JSON.stringify(array) // '["totally","I'm going to mutate you!",{"hammer":"hm, surely I can mutate this nested object..."}]'

instanceof checks can cause issues

Using instanceof to check for arrays etc. can cause issues across frame boundaries. Each document has a different context so one array may not be an instanceof another.

var frame = document.createElement("iframe");

frame.onload = function(e) {
  console.log([] instanceof frame.contentWindow.Array); // false
}

document.body.appendChild(frame);

Cursors

Just wondering before I test this out, I use cursors to great effect alongside the fynx immutable-flux library. I'd definitely want to test this library out as I am only targeting mobile platforms so I don't need old IE support.

Plans for cursors, compat with other cursor impls?

Prod Mode

In production, it's probably unnecessary to actually freeze objects and throw exceptions when mutating methods are invoked. Those are mainly necessary in development, but by the time the code hits production, if any invariants aren't being followed, you should already know about it.

With that in mind, having a way to turn off the freezing and overriding should speed up performance, particularly in Safari. Adding a quick flag to turn this on would be useful to that end.

breaks on a date

Example:

var Immutable = require('seamless-immutable');
var d = { date: new Date() };
console.log(d.date.toISOString());
var d = Immutable(d);
console.log(d.date.toISOString());

Console:

2015-03-18T03:17:29.895Z
/Users/matt/Playground/seamless-immutable/index.js:5
console.log(d.date.toISOString());
                   ^
TypeError: undefined is not a function
    at Object.<anonymous> (/Users/matt/Playground/seamless-immutable/index.js:5:20)
    at Module._compile (module.js:460:26)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:501:10)
    at startup (node.js:129:16)
    at node.js:814:3

Performance Hit while merging really large JS objects even when merge deep is set to false

screen shot 2015-10-20 at 1 32 05 pm

Scenario

Recieved a huge JSON response from an API request.

Problem

While setting the response as a JS object, into an immutable store via merge — the function converts the huge object to an Immutable recurrsively. This is causes performance issues and makes the rendering slow.

Ideally if the user has set {deep: false} at the time of merging, it should not try to convert recursively.

Benchmarks

Someone on Hacker News brought up benchmarks. It would be interesting to see how seamless-immutable compares to other immutable libraries in js-hashtrie-benchmark (which seems like a fair comparison given API similarities, even though it does not happen to use a hashtrie implementation under the hood), particularly once Prod Mode exists.

How should I import this module from es6?

Thanks for this module!

Importing like this in es5: var I = require('seamless-immutable') gives me:

{ [Function: Immutable]
  isImmutable: [Function: isImmutable],
  ImmutableError: [Function: ImmutableError] }

but I do not find a way to get the same thing in es6.
Doing import * as I from 'seamless-immutable' gives:

{ isImmutable: [Function: isImmutable],
  ImmutableError: [Function: ImmutableError],
  default: 
   { [Function: Immutable]
     isImmutable: [Function: isImmutable],
     ImmutableError: [Function: ImmutableError] } }

I can see that what I need is the default inside but I do not manage to get it
Thank you!.

document technique for updating an individual key

with Immutable.js I can do

Immutable.List.of(1,2,3).update(0, x => x + 1)

This is useful if I have view state that is a list of 10 elements and I want to update just one of them that the user acts on.

What is the best practice for accomplishing this with seamless-immutable?

Validate immutable objects

Is there a way to validate that an array is an immutable array? Like something that would be instanceof for immutable arrays but not for normal mutable arrays.

The use case is actually React.Proptypes validation. Something similar to

propTypes: {
    myobject: React.PropTypes.instanceOf(Immutable.List)
}

I know I can use a custom function in ProtoTypes and use Immutable.isImmutable, but adding a function like that in every component that should receive immutable prop objects, will not be pretty.

Have banned methods return a new Immutable instance instead

Hi,

I love the fact that this library is super small and that doesn't try to do anything else than making your data immutable :).

There's only one thing holding me back from using it though:

Immutable([3, 1, 4]).sort()
// This will throw an ImmutableError, because sort() is a mutating method.

Have you considered returning a new immutable structure with the sorted array instead? (the same applies for every other banned method).

Thanks :)
Darío

Handling additional object types

I use moments (moment.js) rather then Date objects and they get lost in translation due to the cloning process. Does it make sense for there to be a way to tell Immutable how to handle additional object types?

using seamless-immutable in a jsbin

I'm trying to use seamless-immutable in this jsbin but whenever this line is ran:

var array = Immutable(["foo", "foo2", "bar", "bar2", "baz", "baz2"]);

I get this error:

Uncaught TypeError: Cannot redefine property: [object Object]

And this stack trace:

addPropertyTo   @   seamless-immutable.js:5
makeMethodReturnImmutable   @   seamless-immutable.js:84
makeImmutableArray  @   seamless-immutable.js:94
Immutable   @   seamless-immutable.js:328
(anonymous function)    @   nesomi.js:36
applyStr    @   ember.debug.js:18054
sendEvent   @   ember.debug.js:12373
exports.default.mixin.Mixin.create.trigger  @   ember.debug.js:29705
superFunction   @   ember.debug.js:13704
EmberObject.default.extend.trigger  @   ember.debug.js:40271
superWrapper    @   ember.debug.js:17586
Renderer.default.didInsertElement   @   ember.debug.js:38923
Renderer_renderTree @   ember.debug.js:8480
scheduledRenderTree @   ember.debug.js:8506
Queue.invoke    @   ember.debug.js:871
Queue.flush @   ember.debug.js:936
DeferredActionQueues.flush  @   ember.debug.js:741
Backburner.end  @   ember.debug.js:166
Backburner.run  @   ember.debug.js:221
executeTimers   @   ember.debug.js:603
(anonymous function)    @   ember.debug.js:592

Add function for deep merge?

I did this a few days ago in a fork:
crudh@8552c98

mergeDeep is like the normal merge function but it merges all children objects too. Right now there is no code reuse from the normal merge (except for 2 lines it is a copy and paste of merge) and the tests are simple copies from the normal merge.

Before cleaning it up for my fork I just wanted to know if there is any interest to pull it back into master? If so, are there any preferences for naming, code reuse and so on?

Missing methods

I love seamless-immutable, but am missing a few methods such as immutablejs' update and setIn. Are there plans to add some? Another option would be to make seamless-immutable pluggable.

Deep merge fails in non-trivial cases

Unless I'm doing something daft (always a strong possibility!). Given

var obj = Immutable({
  streams: {
    a: { name: "Conversations", channel: ["Facebook","Twitter"], loading: false},
    b: { name: "@Mentions", channel: ["Twitter"], loading: false},
    c: { name: "Facebook", channel: ["Facebook"], loading: false}
  },
  streamOrder: ["c", "a", "b"]
})

then running j.merge({streams: {a: { loading: true }}}, true) produces

{
  streams: {
    a: { loading:true }
  },
  streamOrder: {0: "c", 1: "a", 2: "b"}
}

so instead of doing a deep merge it has thrown away all of the data under the streams key! Have I misunderstood how the deep merge is meant to work?

It's also converted the data under streamOder from an Array to an Object, but I can live with that for the time being :-)

Immutable doesn't freeze Date objects.

console test:

var mydate = Immutable(new Date());
undefined
mydate
Fri Jun 12 2015 15:24:24 GMT+1000 (AEST)
mydate.setYear(2020)
1591939464409
mydate
Fri Jun 12 2020 15:24:24 GMT+1000 (AEST)

Last entry shows Date object hasn't been made immutable.

asMutable({deep:true}) doesn't work with date objects.

var test = Immutable({mydate: new Date()})
test.asMutable({deep:true});

result:

Uncaught TypeError: obj.asMutable is not a function
at asDeepMutable (http://localhost:8080/resource-bundles/bowerComponents.resource/seamless-immutable/seamless-immutable.js:215:20)
at Object.asMutableObject (http://localhost:8080/resource-bundles/bowerComponents.resource/seamless-immutable/seamless-immutable.js:304:31)
at Object.eval (eval at evaluate (unknown source), :1:6)
at Object.InjectedScript._evaluateOn (:895:55)
at Object.InjectedScript._evaluateAndWrap (:828:34)
at Object.InjectedScript.evaluateOnCallFrame (:954:21)

Use with AngularJS

I'm attempting to use this in my angularjs app. It doesn't appear to be working. Any clue on how you'd implement it within Angular?

Preserve object prototypes

Currently, if you pass an object with a prototype to Immutable, that prototype is discarded. It seems reasonable to preserve that prototype instead, with the understanding that not all of the methods it exposes will necessarily still work.

This would address #24

merge can create partial Immutables

When merging an immutable object with a mutable object, the resulting immutable object can contain mutable objects, but still identifies as immutable. For example, here's current behavior:

obj = Immutable( { key: 'value', collection: [ 1,2,3 ] } )
Immutable.isImmutable( obj ) //true
Immutable.isImmutable( obj['collection'] ) //true

newObj = obj.merge( { collection: [ 1,2,3 ] } )
Immutable.isImmutable( newObj ) //true
Immutable.isImmutable( newObj[ 'collection' ] ) //false
Immutable.isImmutable( newObj[ 'collection' ].push ) //danger!

its unclear what expected behavior should be. Personally, I think .merge should deeply convert everything that gets passed in to be immutable, as these partially immutable objects can create strange bugs. But maybe we should give users more flexibility. We could just add an isDeeplyImmutable function, and let users deal with the consequences.

Please clarify the docs regarding when dev/prod modes are on (Node)

Hello,

perhaps it wasn't my brightest day :-) but I found it difficult to find out when the dev or prod modes are active under Node.js. It would be great clarify that in the docs.

I believe this is true (and exhaustive) but am not sure:

/*1.*/ var Immutable = require('seamless-immutable'); // => dev mode
/*2.*/ var Immutable = require('seamless-immutable/seamless-immutable.production.min'); // => prod mode
/*3.*/ var Immutable = require('seamless-immutable/seamless-immutable.production.min'); process.env.NODE_ENV = "development"; // => dev mode

Thank you!

Internet Explorer 8 compatibility?

I noticed the compatibility list has IE9 and up, but does not list IE8. Unfortunately we have to support that. Has anyone actually tested seamless-immutable in IE8?

Question about `.push`

What do you think about making push immutable operation (returning new extended array) instead of blocking it? The drawback is that you change conventional (but broken) API, but the benefit is that people could stop to use concat where push will satisfy. My arguments:

  1. Barely noone is rely on native push return value (updated array length 😞) right now so this API "change" shouldn't surprise or hurt.
  2. You don't modify Array.prototype so this is safe.
  3. You already added some unexisting but crucial methods like flatMap
    API is already changed why not fix push then.

Map returns an Immutable object

I don't really understand why .map is wrapped to return an Immutable object. This seems needlessly restricting. In combination with #16, this also causes unexpected stack overflow errors.

Immutable([1,2,3]).map(function() {
   return React.createElement( "div", null );
});
// too much recursion

Custom merger function

I have a use case where I would like to override the normal merge behaviour for specific types or conditions.

I was thinking that you could send a custom merger function as a parameter with the config to merge. If that function returns undefined the merge would proceed as normal. If it returns something else then that value will be used instead of the normal rules.

A simple example that combines arrays instead of replacing them:

var one = {
  id: 3,
  list: [1, 3, 5]
};

var two = {
  id: 3,
  list: [2, 4, 6]
};

var arrayConcatMerger = function(current, other) {
  if (current instanceof Array && other instanceof Array) {
    return current.concat(other);
  }

  return;
}

var result = one.merge(two, {merger: arrayConcatMerger});

And the result would be:

 {
  id: 3,
  list: [1, 3, 5, 2, 4, 6]
};

This would allow you to do more complex stuff like merging objects in an array if there exists an object with the same id in both arrays and so on.

should objects with custom prototypes really get immutablized?

After playing with this a bit I realized I really don't think objects with custom prototypes should get Immutableized, recursively or whatever. The most common thing I've run into is trying to pass in myImmutable.map(item => (<MyComponent {...item}/>)) as the children of a React component. map stack overflows on trying to Immutable()-ize the React elements. What are the use cases for trying to immutablize objects with custom prototypes? Even Immutable.js allows you to stick non-immutable things into an immutable type. I prefer this concept of shallow immutability.

merge should return the same object when no meaningful changes were applied

I think it would be helpful if merge returned the same object when no changes were made. Here's what I mean:

obj  = Immutable({ key: 'value' })
obj1 = obj.merge({ key: 'new value '}) 
obj2 = obj.merge({ key: 'value' }) //no meaningful change

// current behavior
obj === obj1 //false
obj === obj2 //false

// ideal behavior
obj === obj1 //false
obj === obj2 //true

Immutable.js provides this feature with their 'set' function, which I've found helpful in the past.

This allows users to run quick checks to see if anything has changed after a merge, which would, for instance, be useful in the context of react/flux. While the check can be done after the fact (using a deepEquals function or something like that), combining that logic with merge has obvious performance benefits.

Maybe both versions of merge should be included in the public API, e.g. 'merge' and 'safeMerge' -- but that may also be overkill. Interested to hear thoughts on this.

How to modify an immutable array?

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!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.