tc39 / proposal-deep-path-properties-for-record

ECMAScript proposal for deep spread syntax for Records
93 stars 2 forks source link

Should deep path properties work in Objects too? #11

Open littledan opened 4 years ago

littledan commented 4 years ago

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.

ljharb commented 4 years ago

I think it would be weird if an often-requested object feature were not made available on objects.

littledan commented 4 years ago

@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.

ljharb commented 4 years ago

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).

erights commented 4 years ago

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.

erights commented 4 years ago

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.

mbellman commented 3 years ago

@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:

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. : - )

llllvvuu commented 1 year ago

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.