Open littledan opened 4 years ago
I think it would be weird if an often-requested object feature were not made available on objects.
@ljharb Do you have any cross-references to those requests, so we can understand their needs better? This could help us get the semantics right for those cases.
I don't - they're quite hard to search for. In particular, there's often been requests on es-discuss for a way to set a deep property, creating objects along the way, rather than having to explicitly create each step (mkdir -p
versus just mkdir
).
At first I did ask that deep-path notation work for objects with no extra notation. @littledan explained why it was too surprising. Once I got it, I agreed (and agree) that without extra notation it would indeed be too surprising. I suggested notation that does not work, but it is still an example of the kind of notation I would look for. So, to start the brainstorming, my invalid suggestion was:
const state2 = #{
...{...{...state1}},
counters[0].value: 2,
counters[1].value: 1,
metadata.lastUpdate: 1584383011300,
};
Because the deepest path is three deep, the notation at the ...
location should indicate that state1
's contents are being spread into state2
three deep, whereas the normal ...state1
notation only spreads state1
one deep. If the spread notation does not imply a depth at least as great as the deepest path, the expression would be statically rejected.
Until we discover such a notation, I agree with the status quo position: that deep path notation is suitable for records and tuples with no hazard, but is too hazardous to apply directly to objects.
Since I'm suggesting brainstorming on notation, I'll make a suggest I do not like:
const state2 = #{
.....state1,
counters[0].value: 2,
counters[1].value: 1,
metadata.lastUpdate: 1584383011300,
};
where each additional .
beyond the first three would always spread and reconstruct the object one more level deep.
@erights @littledan Could either of you elaborate on the reasoning behind excluding the notation from regular Objects? The arguments I've seen so far are:
Record
, where (because Records
are immutable) the semantics are much simpler." (readme)I'm curious to know what's surprising/hazardous/semantically unclear about applying the same principle to Objects. Is it something to do with the ambiguity around whether a nested property is an array-like or object-like, and having to use the correct immutable update helper to perform a value substitution for the receiver, without mutating the reference?
For example:
const b = {
...a,
deep.list[2].value: 5
};
In this case, a.deep.list
could be either an array/array-like or an object/object-like with numeric keys, and I suppose it would be uncertain whether b.deep.list
would have to be reconstructed from _objectSpread
or something like an immutable _replaceInArray
helper.
Evidently, Records
and Tuples
restrict the problem domain to just objects and arrays. Is this the crux of the difference? Having to determine whether a value is an Array
/Object
/arguments
/NodeList
/Map
/Set
/Proxy
/et. al., or just a Record
vs. a Tuple
? Perhaps the argument is whether coercion/conversion to a plain Array
or Object
from an expanded set of array-likes and object-likes is worth supporting at the user's peril. After all, you can write [...object]
or {...array}
as it is.
Or maybe this isn't the counterpoint at all. : - )
In this case,
a.deep.list
could be either an array/array-like or an object/object-like with numeric keys
Wouldn't this have to be checked at run-time either way? (regardless if Record
or Object
)
Or maybe this isn't the counterpoint at all. : - )
I suspect (trying to follow this comment) the issue has to do with referential equality of the sub-objects. Probably something about mutating nested properties like b.c.d = e
leading to a
being mutated. I haven't figured out how this is worse than un-nested spread assignment though or been able to construct a plausible user error.
When we discussed this proposal with @erights, he suggested that it should be permitted in objects too. I'd prefer to omit objects, as described in #3 , but especially because deep paths implicitly "clone" an object through the equivalent of
{...obj}
freely, as if it's the identity. While this is the identity for Records and Tuples, that's not the case for Objects, which lose many things about them. I think it would be weird if deep paths implicitly made many of those go away. In the discussion, we couldn't think of a good syntax which would express the way that, on objects, you'd be spreading away n levels of detail from the object.