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

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

What happens if the deep path does not exist in the value that is spread? #4

Closed littledan closed 4 years ago

littledan commented 4 years ago

If we restrict deep paths to Records and Tuples, then the difficult part of this question is: If there's a number-like key used in the path, then is a Record or Tuple materialized? Either would be valid--Records can have number-like keys.

Because of this ambiguity, I'd argue we should throw a TypeError in this case. It's always possible to use spread to insert the key first, as well as other operations to test for its presence. But I'd be interested in more feedback on whether this is a very common issue which really needs addressing.

rickbutton commented 4 years ago

This would also need to apply in the reverse:

const rec = #{ a: 1 };

const two = #{ [0]: 2, ...rec }; // TypeError, as you described
const rec = #{ a: #[1,2,3] };
const two = #{ ...rec, a[0]: 2 }; // two === # { a: #[2,2,3] }
const three = #{ ...rec, a.foo }; // TypeError, because we can't add a non number-like key to a tuple
littledan commented 4 years ago
const rec = #{ a: 1 };

const two = #{ [0]: 2, ...rec }; // TypeError, as you described

I don't understand this case. This one is clear: The record will have a property named "0". This is just the direct semantics of the record proposal.

Remember, in all these cases, that you can use deep paths without spread. (There should probably be examples of this in the explainer.) So, #{ a.b.c: 1 } is equivalent to #{ a: #{ b: #{ c: 1 } } }. So, the case I was getting at that would be confusing is something like

#{ a[0]: 1 }
// Two possibilities for expansion:
#{ a: #{ [0]: 1 } }
#{ a: #[ 1 ] }

I agree that adding a non-number-like property to a Tuple also needs to be a TypeError.

rickbutton commented 4 years ago

I've added a couple of FAQ entries to clarify this, let me know if you think they are sufficient.

littledan commented 4 years ago

The FAQ entry looks good, but I'd suggest we ban all non-existent paths, not just numeric-like ones (just to keep it simple and consistent, but I'd be open to reconsidering).

rickbutton commented 4 years ago

Good point, that does simplify things somewhat. I've updated it again to reflect that.

littledan commented 4 years ago

Minor suggestion: I think it'd be clearest if you opened with an example of such a manipulation (doesn't matter whether it's numeric or not) and state that it's a TypeError. Then, the rationale can be below it. Materialization is never a thing, so we can start by saying that, rather than getting into the nitty-gritty of how it would work if it did exist.

rickbutton commented 4 years ago

good idea! I've moved the examples ahead of the details.

littledan commented 4 years ago

looks good; made some minor edits in #6.

rickbutton commented 4 years ago

thank you, the edits look great, merged

ByteEater-pl commented 4 years ago

If reconsidered, a possibility would be to use t[n] for tuples and r.[n] for records.

littledan commented 4 years ago

@ByteEater-pl In the Records and Tuples proposal, the syntax for accessing Tuples is t[n], and Records (just like objects) can also use r["n"].

ByteEater-pl commented 4 years ago

There's no difference between r[3] and r["3"], is there? I assume the index gets stringified first, just like with property access. But indeed, giving those different semantics and branching on the type inside brackets (probably requiring indexness too) is another option. Programmers would simply add + in the front if they want a tuple. A gotcha for the unaware, though in quite a corner case.

the syntax for accessing Tuples is t[n]

Indeed, and I don't propose to change it, nor to prevent r[n] from working if ToString(n) is numeric (and probably only up to 2**32-2 or 2**53-1). The additional syntax r.[n] would be available only in spreads and result in a record being materialized.

littledan commented 4 years ago

I find this syntax for explicitly asking for materialization to be very confusing. I'd suggest that people use (nested) record literal syntax in this case.

ByteEater-pl commented 4 years ago

I'd suggest that people use (nested) record literal syntax in this case.

That also works. And since it's a corner case anyway, nothing else seems indispensable.

I'd suggest that people use (nested) record literal syntax in this case.

Do I understand correctly that you suggest to allow only r[n] syntax for computed keys and have it materialize records for non-index values of n but tuples otherwise? I'd much prefer the consistency of it always being a record (lest the gotcha I mentioned previously looms here too) and requiring programmers to reach for (nested) tuple syntax explicitly if that's what they want – in the same vein as your solution but with reversed semantics.

littledan commented 4 years ago

Any materialization would have to be done by the programmer; nothing will come implicitly in this proposal.

mmkal commented 3 years ago

@littledan @rickbutton I've had a look at the FAQ and this issue and I'm still a little confused. Couldn't the number key ambiguity be avoided by just... picking a behaviour and including it in the spec? e.g. "if a deep path with a number key does not already exist, a tuple will be materialized"? People who really want to use number keys in records (pretty rare IRL, I suspect) could just use a slightly more verbose syntax.

To use the examples in the explainer:

const one = #{ a: #{} };

#{ ...one, a.b.c: "foo" }; // returns #{ a: #{ b: #{ c: "foo" } } }
#{ ...one, a.b[0]: "foo" }; // returns #{ a: #{ b: #["foo"] }

#{ ...one: #{ b: #{ [0]: "foo" } } // what you have to do if you're a weirdo who likes number keys in records

I might be missing something though.

I'm also confused by this:

Remember, in all these cases, that you can use deep paths without spread. (There should probably be examples of this in the explainer.) So, #{ a.b.c: 1 } is equivalent to #{ a: #{ b: #{ c: 1 } } }. So, the case I was getting at that would be confusing is something like

Does this mean #{ a.b.c: 1 } will be valid under this proposal? If so, don't we have the same problem? i.e. ambiguity in the meaning of #{ a.b[0]: 1 }?