You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Conducting deep merges with Set values is inherently difficult to do for multiple reasons. This difficulty is compounded with the immutable data structure that immuta builds using ES6 Proxies.
tl;dr - Set works, but it is likely to cause unwanted effects. Use Array instead where possible or modify the set using recursion manually and never assume Set given from immuta is going to preserve the order they were added in.
Possible Solution?
Possible solution first since most of us have some form of ADD and prob can't handle all the words below without understanding what the end looks like ;-) (or maybe that's just me!).
The only possibility I can imagine of making Set work exactly as expected would be to rebuild Set using an array when we receive one, manage the lifecycle throughout the mutations that are given, and return a Set which maintains the insertion order.
This would end up with essentially a double-proxy upon Set's and would require maintaining a few extra levels of state within our state descriptors.
The Problem
The root of our problem is that Set doesn't have (and has no reason to have) any mechanism to "get" values. We also can't rely on the order of values due to the fact Set guarantees that all values in the Set are unique.
Take this case:
constk1={foo: 1};constk2={bar: 2};constk3={baz: 3};constset=newSet([k1,k2,k3]);// now lets consider a situation where // we do something like:set.add(k1);
In this example, the set is unchanged in the last .add() as k1 already exists within the set. So while we might assume k1 is where we expect it to be, there are many situations where it isn't if we aren't extremely careful with them.
In addition to this, our only real mechanism for capturing the Proxy objects inside of this set are to iterate the set manually. We do not generate any proxies or modify anything until you read so until you iterate the set, its just a native Set { k1, k2, k3 }.
You may be surprised in this case what the result is. Since we need to modify the object reference deeply, the only possibility is to remove the base value and add our shallow copied copy to the set, changing the ordering in this case:
Or if we print the Set's of state and next directly:
Because of this, simply iterating the new set and merging with values in the same index of the source set (which is how we originally planned to do this) is problematic.
Lets make matters worse
We actually have further issues here. Since even checking if a value exists on Set means you must already have a reference to that value (if its an object), we actually can't really ever properly proxy against a Set's values unless we iterate it (using something like forEach or for...of. We handle this fine in immuta's standard functions (as shown above) where we iterate and optionally mutate, but merge either would need to keep pretty extensive history throughout the lifecycle, or it would need to simply be transformed into an array in order to be any kind of efficient here.
All-in-all - Set is simply not generally a good idea and I am not sure I see any solution that can be made easy to use.
This is why, in the end, merging sets in general is problematic.
How it Works Now
For now, mergeWithDraft will simply add any values that did not previous exist into the draft. This can definitely cause unwanted issues, and it may need to be changed to either ignore or replace the Set all together (with a warning).
Take this example where we may want to mutate the k1 directly, in this case we might try something like that would work with arrays like this:
Set keys must be unique (only adds qux once when calling new Set() here.
We can not guarantee that the order is what you expect it to be, so we can't assume that iterating the current set will produce the object you want to merge with.
We end up checking if qux is on set, and add it to the Set if it doesn't.
ES6 Map is a little bit different. immuta treats the keys of Map as they are. We do not proxy them and we do not attempt to provide specific drafts for them. They are not immutable and any attempt to do this introduces an insane amount of possible issues and side effects (trust me, we tried ;-))
Since we have .get() the merge works exactly how we would expect and can be done fairly efficiently. Note that we do need to proxy the function calls for map - but this can work out to an advantage in some cases.
One interesting property of how we handle things is that WeakMap is actually possible as long as you create the WeakMap originally then switch to using maps!
Summary
Conducting deep merges with Set values is inherently difficult to do for multiple reasons. This difficulty is compounded with the immutable data structure that
immuta
builds using ES6 Proxies.tl;dr -
Set
works, but it is likely to cause unwanted effects. UseArray
instead where possible or modify the set using recursion manually and never assumeSet
given fromimmuta
is going to preserve the order they were added in.Possible Solution?
Possible solution first since most of us have some form of ADD and prob can't handle all the words below without understanding what the end looks like ;-) (or maybe that's just me!).
The only possibility I can imagine of making
Set
work exactly as expected would be to rebuildSet
using an array when we receive one, manage the lifecycle throughout the mutations that are given, and return aSet
which maintains the insertion order.This would end up with essentially a double-proxy upon Set's and would require maintaining a few extra levels of state within our
state descriptors
.The Problem
The root of our problem is that
Set
doesn't have (and has no reason to have) any mechanism to "get" values. We also can't rely on the order of values due to the factSet
guarantees that all values in the Set are unique.Take this case:
In this example, the set is unchanged in the last
.add()
ask1
already exists within the set. So while we might assumek1
is where we expect it to be, there are many situations where it isn't if we aren't extremely careful with them.In addition to this, our only real mechanism for capturing the
Proxy
objects inside of this set are to iterate the set manually. We do not generate any proxies or modify anything until you read so until you iterate the set, its just a nativeSet { k1, k2, k3 }
.Say you did something like:
You may be surprised in this case what the result is. Since we need to modify the object reference deeply, the only possibility is to remove the
base
value and add our shallow copiedcopy
to the set, changing the ordering in this case:Or if we print the
Set
's ofstate
andnext
directly:Because of this, simply iterating the new set and merging with values in the same index of the source set (which is how we originally planned to do this) is problematic.
Lets make matters worse
We actually have further issues here. Since even checking if a value exists on
Set
means you must already have a reference to that value (if its an object), we actually can't really ever properly proxy against a Set's values unless we iterate it (using something likeforEach
orfor...of
. We handle this fine inimmuta
's standard functions (as shown above) where we iterate and optionally mutate, but merge either would need to keep pretty extensive history throughout the lifecycle, or it would need to simply be transformed into an array in order to be any kind of efficient here.All-in-all -
Set
is simply not generally a good idea and I am not sure I see any solution that can be made easy to use.This is why, in the end, merging sets in general is problematic.
How it Works Now
For now,
mergeWithDraft
will simply add any values that did not previous exist into the draft. This can definitely cause unwanted issues, and it may need to be changed to either ignore or replace theSet
all together (with a warning).Take this example where we may want to mutate the
k1
directly, in this case we might try something like that would work with arrays like this:Which would ultimately end with:
This is due to:
Set
keys must be unique (only adds qux once when callingnew Set()
here.qux
is on set, and add it to theSet
if it doesn't.If we compare to the same with array:
We likely end with the exact results you are expecting:
What about ES6 Map?
ES6 Map is a little bit different.
immuta
treats the keys ofMap
as they are. We do not proxy them and we do not attempt to provide specific drafts for them. They are not immutable and any attempt to do this introduces an insane amount of possible issues and side effects (trust me, we tried ;-))Since we have
.get()
the merge works exactly how we would expect and can be done fairly efficiently. Note that we do need to proxy the function calls for map - but this can work out to an advantage in some cases.With the
printDifference
looking like:Also note that in this example, we would probably benefit from using
.at
version ofmergeWithDraft
Anyway, this has gotten quite long.
The text was updated successfully, but these errors were encountered: