fail-fast object destructuring (don't add more slop to sloppy mode)

# Brendan Eich (6 years ago)

Andreas Rossberg advocated "fail-fast object destructuring", and Dave Herman started a thread last June at

esdiscuss/2012-June/023710

and ending near here:

esdiscuss/2012-July/024043

I'd like to revive this as an open issue for ES6.

With the recent mega-thread on "excluding features from sloppy mode" (tail post: esdiscuss/2012-December/027746), I believe that everyone involved agrees that we should not add more slop in ES6, where new slop might then require more strictness in a future strict mode.

We can't afford the spec and implementation and user-brainprint costs of a never-ending lattice of sloppy and strict combinations. Worse, runtime changes in a stricter strict will create the same hazards for ES6-stricter-strict vs. ES5 strict that we had with ES5 strict vs. ES3.

So where is new slop in ES6? The obvious place is destructuring, proposed for ES4 and prototyped in Opera (array patterns only), and then in SpiderMonkey & Rhino. Destructuring as proposed is thin syntax for property references on the right of assignment expressions or binding initializations:

js> let o = {p: 42}

js> let p = o.p

js> p

42 js> let {p:q} = o

js> q

42

Thus it follows that if o has no property p, the p and q bindings are initialized to undefined:

js> let o = {r: 42}

js> let p = o.p

js> p

js> let {p:q} = o

js> q

js>

This is future-hostile to any pattern-matching construct (strawman:pattern_matching), we want the notation to fail by throwing in the binding and assignment cases, so that a match construct can fail over to the next pattern.

Even ignoring the future, this fail-soft approach, while consistent with missing property handling in JS, is slop that tends to hide bugs or make them hard to fix -- the undefined flows far afield before being dereferenced (assume an explicit toString(radix) call on p or q) and inevitably throwing.

(Really, I made JS impute undefined for missing properties back in those ten days in May 1995 because I had no time to add matching, try-catch, existential operator syntax, or any other means for programmers to say when they wanted to "object-detect" rather than fail-soft. If I had had more time, I'd have done something better. Developers get burned by typos too often. When we added try/catch exception handling in ES3, we did not revisit this primal sin.)

The other kind of slop to which Andreas objected is the implicit ToObject call on the right-hand side of the '=' in a destructuring binding with initialiser, or a destructuring assignment expression. That's just another implicit conversion, the bane of JS developers' existence which strict mode can't banish. Such implicit conversion does help code of the form that Allen sketched at esdiscuss/2012-June/023719.html:

let {concat, indexOf, lastIndexOf} = ""; //get some string methods

Of course, this is a hard case and easily rewritten to use new String("") or String.prototype or equivalent.

We talked about ? prefixing of identifiers in patterns, vs. ! for irrefutable (give me undefined if missing) prefixing, which is simply future-hostile to pattern matching. The slop-free and pattern-friendly changes from ES4&6 destructuring would thus entail:

  1. No ToObject(RHS).
  2. Exception on missing property selected without ?-prefix.

We thought a ? suffix was ambiguous, or just confusing, in object literals, since the grammar for expressions must cover the grammar for patterns, but there was no formal ambiguity. I recall Dave at least proposing a ?-suffix at first.

Meanwhile, TypeScript has appeared, and it uses ? after identifiers in its structural types (interfaces):

interface Friend { name: string; favoriteColor?: string; }

I think the refutable-by-default patterns need a ? modifier only for a "leaf" identifier in a pattern, not for non-terminal identiifers. For example:

let {p: {q: r}} = deep_o;

wants to get o.p.q and bind it to r.

With the current irrefutable design, if deep_o = {} or another object lacking a {q:...} sub-object named p, then this example will fail with a TypeError even though a shallow example fails-soft (or fails slowly, really: fails later) by pulling undefined out as the value of p and binding it to the local (as in the first example in this post).

With a refutable-by-default design, however, it seems to me nonsensical to support ? on p as well as q in this example:

let {p?: {q?: r}} = deep_o;

because if deep_o has no property p, what should happen? No binding r at all? Or a let binding named r with value undefined? What if there's no ? after q in this case -- should that matter?\

We already rejected CoffeeScript's existential operator because of its lack of compositionality. See esdiscuss/2012-September/025232 (the section starting "ARB: This is non-compositional"). I think the same objection applies to allowing p? in the deep_o case.

I'd appreciate feedback on my thinking here, in case the objection does not apply.

In summary, I think the right new-new plan to avoid adding slop (and all thanks to Andreas and Dave for keeping this torch aloft) is to revise ES6 destructuring thus:

  1. No ToObject(RHS).
  2. Exception on missing property selected without a ?-suffix.
  3. ?-suffix allowed only on leaf pattern identifiers (short or long-hand). 4: the ? is a separate lexeme from the : in long-hand patterns.

Comments welcome. I'll put this

# Herby Vojčík (6 years ago)

Brendan Eich wrote:

I think the refutable-by-default patterns need a ? modifier only for a "leaf" identifier in a pattern, not for non-terminal identiifers. For example:

let {p: {q: r}} = deep_o;

wants to get o.p.q and bind it to r.

With the current irrefutable design, if deep_o = {} or another object lacking a {q:...} sub-object named p, then this example will fail with a TypeError even though a shallow example fails-soft (or fails slowly, really: fails later) by pulling undefined out as the value of p and binding it to the local (as in the first example in this post).

With a refutable-by-default design, however, it seems to me nonsensical to support ? on p as well as q in this example:

let {p?: {q?: r}} = deep_o;

because if deep_o has no property p, what should happen? No binding r at all? Or a let binding named r with value undefined? What if there's no ? after q in this case -- should that matter?\

We already rejected CoffeeScript's existential operator because of its lack of compositionality. See esdiscuss/2012-September/025232 (the section starting "ARB: This is non-compositional"). I think the same objection applies to allowing p? in the deep_o case.

I advised adding Null Pattern Object into language in the aforementioned thread; and use foo? as foo == null ? NullPatternObject : foo.

So you can do r = o?.p.q; and it does not fail. With such construct you could do let {p: {q: r}} = deep_o?;

# Brendan Eich (6 years ago)

Herby Vojčík wrote:

I advised adding Null Pattern Object into language in the aforementioned thread; and use foo? as foo == null ? NullPatternObject : foo.

So you can do r = o?.p.q; and it does not fail.

Separate topic, and we considered it as an alternative to CoffeeScript's non-compositional rewrite-based semantics at the September TC39 meeting, but did not proceed. Thanks for the reminder, I will update strawman:existential_operator.

With such construct you could do let {p: {q: r}} = deep_o?;

This does not address the problem for destructuring. It's true a Nil object (as Brandon Benvie prototyped: Benvie/nil/blob/master/nil.js) can be deeply destructured. But there is no refutable match future. We need an exception on missing property, not a magic get/set-sink object.

The suffix-? syntax in expressions is problematic in combination with missing semicolons. You get another case where programmers expect ASI but there's no error to correct:

x = y? z:w

where z is label and w is a statement that can syntactically (if not sensibly) be labeled.

# Herby Vojčík (6 years ago)

Brendan Eich wrote:

Herby Vojčík wrote:

I advised adding Null Pattern Object into language in the aforementioned thread; and use foo? as foo == null ? NullPatternObject : foo.

With such construct you could do let {p: {q: r}} = deep_o?;

This does not address the problem for destructuring. It's true a Nil object (as Brandon Benvie prototyped: Benvie/nil/blob/master/nil.js) can be deeply destructured.

That is what I meant.

But there is no refutable match future. We need an

I don't understand. :-/

exception on missing property, not a magic get/set-sink object.

Without ?, it can throw. With it, it does not because of how Nil works.

The suffix-? syntax in expressions is problematic in combination with missing semicolons. You get another case where programmers expect ASI but there's no error to correct:

x = y? z:w

where z is label and w is a statement that can syntactically (if not sensibly) be labeled.

... or whatever other syntax. It's about Null Pattern idea, not the actual syntax.

# Brendan Eich (6 years ago)

Herby Vojčík wrote:

This does not address the problem for destructuring. It's true a Nil object (as Brandon Benvie prototyped: Benvie/nil/blob/master/nil.js) can be deeply destructured.

That is what I meant.

Thought so ;-).

But there is no refutable match future. We need an

I don't understand. :-/

We want a way to match with patterns like so:

match (expr) { case {must, may?} => ... case {always} => ... }

In other words, the ? must go in the LHS pattern language, not on the RHS of a destructuring binding or assignment expression.

missing semicolons. You get another case where programmers expect ASI but there's no error to correct:

x = y? z:w

where z is label and w is a statement that can syntactically (if not sensibly) be labeled.

... or whatever other syntax. It's about Null Pattern idea, not the actual syntax.

Ok, but we need a concrete syntax that works if we want anything like CoffeeScript's suffix-? operator. And I agree suffix-? is attractive. But it seems like a non-starter based on the use of ? in ?: and : in labeled statements. Perhaps there's a tweak that saves this concrete syntax, though?

# Herby Vojčík (6 years ago)

Brendan Eich wrote:

Herby Vojčík wrote:

But there is no refutable match future. We need an

I don't understand. :-/

We want a way to match with patterns like so:

match (expr) { case {must, may?} => ... case {always} => ... }

In other words, the ? must go in the LHS pattern language, not on the RHS of a destructuring binding or assignment expression.

I see. You need o.?p more than o?.p, so you can write (informally) o.{foo, ?bar}.

What about the above, then? So you can write foo.?bar to mean foo.bar == null ? Nil : foo.bar. Then it naturally follows that you can as well do let {must:must, ?may:may} = o which can be shortened. And it can go deeper, since it is Nil.

(but again, syntax may conflict with existing use)

# Brendan Eich (6 years ago)

Herby Vojčík wrote:

Then it naturally follows that you can as well do let {must:must, ?may:may} = o which can be shortened.

Yes, that was the syntax we talked about earlier this year, but I think CoffeeScript and TypeScript make a case for suffix-?.

And it can go deeper, since it is Nil.

What is Nil? There is no requirement with ? in the pattern language (on the LHS) for the RHS to be of any particular type.

I like Nil, and it may help rescue ?. the existential operator strawman. But that is in the expression language, not in the pattern language.

# Herby Vojčík (6 years ago)

Brendan Eich wrote:

Herby Vojčík wrote:

Then it naturally follows that you can as well do let {must:must, ?may:may} = o which can be shortened.

Yes, that was the syntax we talked about earlier this year, but I think CoffeeScript and TypeScript make a case for suffix-?.

And it can go deeper, since it is Nil.

What is Nil? There is no requirement with ? in the pattern language (on the LHS) for the RHS to be of any particular type.

I like Nil, and it may help rescue ?. the existential operator strawman. But that is in the expression language, not in the pattern language.

I like not to make things unnecessarily disjoint. Here I see the possibility of having pattern language (in future patterns as well as in present destructuring) having same semantics as the expression language with_throwing_on_nonexistent_property (that is what you asked in this thread, unless I did not understand).

That is, these are identical, except the syntax:

r = o.p.q	{p: {q: r}} = o
r = o.?p.q	{?p: {q: r}} = o
P=o.p; Q=o.?q	{p: P, ?q: Q} = o

With "it can go deeper, since it is Nil" I meant the second line, where (o.?p) produces Nil, so o.?p.q being Nil.q produces Nil as well, without throw.

# Brendan Eich (6 years ago)

Herby Vojčík wrote:

Brendan Eich wrote:

Herby Vojčík wrote:

Then it naturally follows that you can as well do let {must:must, ?may:may} = o which can be shortened.

Yes, that was the syntax we talked about earlier this year, but I think CoffeeScript and TypeScript make a case for suffix-?.

And it can go deeper, since it is Nil.

What is Nil? There is no requirement with ? in the pattern language (on the LHS) for the RHS to be of any particular type.

I like Nil, and it may help rescue ?. the existential operator strawman. But that is in the expression language, not in the pattern language.

I like not to make things unnecessarily disjoint. Here I see the possibility of having pattern language (in future patterns as well as in present destructuring) having same semantics as the expression language with_throwing_on_nonexistent_property (that is what you asked in this thread, unless I did not understand).

I'm with you on wanting equivalences, such that "same semantics" works up to some limit (proxies, perhaps -- I'm not sure). Since pattern-matching is in the future it is possible it could diverge observably from equivalent expression language, for good reason.

If there's no reason in the future to diverge, great.

We need to detail how Nil works, how it cannot be wrapped or observed, etc. in order to maintain equivalence.

That is, these are identical, except the syntax:

r = o.p.q    {p: {q: r}} = o
r = o.?p.q    {?p: {q: r}} = o
P=o.p; Q=o.?q    {p: P, ?q: Q} = o

Here I part company only on syntax:

 r = o?.p.q       {p?: {q: r}} = o
 P=o.p; Q=o?.q    {p: P, q?: Q} = o

And of course, the short-hand works:

 p=o.p; q=o?.q    {p, q?} = o

With "it can go deeper, since it is Nil" I meant the second line, where (o.?p) produces Nil, so o.?p.q being Nil.q produces Nil as well, without throw.

Yes, I agree (already :-P) that this is a good idea for saving the existential operator.

# Brandon Benvie (6 years ago)

Just for fun and to experiment with, I added nil to Continuum to play around with. It's similar to the one I made for V8, but a bit more complete. It coerces to the empty string or 0, is callable and constructable (returns self), returns self for all property access, has no prototype, is typeof 'undefined', and falsey. You can access via:

import { nil } from '@continuum';

At benvie.github.com/continuum

# Herby Vojčík (6 years ago)

Brendan Eich wrote:

Herby Vojčík wrote:

Brendan Eich wrote:

I like Nil, and it may help rescue ?. the existential operator strawman. But that is in the expression language, not in the pattern language.

I like not to make things unnecessarily disjoint. Here I see the possibility of having pattern language (in future patterns as well as in present destructuring) having same semantics as the expression language with_throwing_on_nonexistent_property (that is what you asked in this thread, unless I did not understand).

I'm with you on wanting equivalences, such that "same semantics" works up to some limit (proxies, perhaps -- I'm not sure). Since pattern-matching is in the future it is possible it could diverge observably from equivalent expression language, for good reason.

If there's no reason in the future to diverge, great.

In the short example you sketched, with case {must, ?may} there is no need to diverge from the destructuring; so I did not even imagine such divergence for the moment.

We need to detail how Nil works, how it cannot be wrapped or observed, etc. in order to maintain equivalence.

In my naive view, [[GetP]] returns Nil, [[SetP]] does nothing, [[Call]] return Nil. But there are sure some nasty details down there.

That is, these are identical, except the syntax:

r = o.p.q {p: {q: r}} = o r = o.?p.q {?p: {q: r}} = o P=o.p; Q=o.?q {p: P, ?q: Q} = o

Here I part company only on syntax:

r = o?.p.q {p?: {q: r}} = o P=o.p; Q=o?.q {p: P, q?: Q} = o

And of course, the short-hand works:

p=o.p; q=o?.q {p, q?} = o

Well, if it must be this way. I mentally joined the ? with the dot where the operation occurs... but now that I think of it, it doesn't matter if it is "take o, take its p and make it Nil if nonexistent, take its q" early nilling of mine and "take o, take its p, make it Nil if null&Co., take its q" postponed nilling of yours.

I feared it could make mental errors, but it will not.

# Herby Vojčík (6 years ago)

Brandon Benvie wrote:

Just for fun and to experiment with, I added nil to Continuum to play around with. It's similar to the one I made for V8, but a bit more complete. It coerces to the empty string or 0, is callable and constructable (returns self), returns self for all property access, has no prototype, is typeof 'undefined', and falsey. You can access via:

I should also == null and == undefined.

# Brandon Benvie (6 years ago)

Oh yeah forgot to mention that, but it does that too. Does not === either (as I think would be desired).

# Brandon Benvie (6 years ago)

For comparison, here's the internal methods that I currently have implemented, which line up with what you said. Benvie/continuum/blob/gh-pages/engine/object-model/%24Nil.js#L105

# Russell Leggett (6 years ago)

On Wed, Jan 2, 2013 at 1:13 PM, Brendan Eich <brendan at mozilla.com> wrote:

Here I part company only on syntax:

r = o?.p.q       {p?: {q: r}} = o
P=o.p; Q=o?.q    {p: P, q?: Q} = o

And of course, the short-hand works:

p=o.p; q=o?.q    {p, q?} = o

I love this. +1000.

As I've said previously, I think pattern matching would pair exceptionally well with EcmaScript, and refutable destructuring is a great first step towards that. After a little bit of use, I expect the community to demand it, even if they aren't familiar with it in other languages.

"I want to destructure here, but I have a couple of possible options. If only there was some kind of switch case with pattern matching..."

No chance of getting pattern matching in for ES6 by any chance, is there? ;)

# Kevin Smith (6 years ago)

Here I part company only on syntax:

r = o?.p.q       {p?: {q: r}} = o

Interpreted this way, any additional irrefutable markers in a subtree under a refutable identifier become redundant, correct?

r = o?.p?.q    { p?: { q?: r } } = o

Using Nil, the "q" in all cases is "present" (evaluating to Nil), so the "?" in "q?" has no effect. Is that right?

# Kevin Smith (6 years ago)

Interpreted this way, any additional irrefutable markers in a subtree under a refutable identifier become redundant, correct?

Er, meant this:

Interpreted this way, any additional irrefutable markers in a subtree under an irrefutable identifier become redundant, correct?

# Brendan Eich (6 years ago)

Kevin Smith wrote:

Interpreted this way, any additional irrefutable markers in a
subtree under a refutable identifier become redundant, correct?

Er, meant this:

Interpreted this way, any additional irrefutable markers in a subtree under an irrefutable identifier become redundant, correct?

For the proposal to use Nil for the expression semantics, yes.

You're right, this implies destructuring binding forms behave in a way that I flagged as possibly not wanted:

let {p?: {q: r}} = o;

would bind r to undefined for any o that doesn't have a p or that does but o.p doesn't have a q.

On second look this is not as bad as I thought. It would be bad if r were not bound (so an outer r could become visible) depending on o's dynamics. That seems right out!

# Herby Vojčík (6 years ago)

Brendan Eich wrote:

Kevin Smith wrote:

Interpreted this way, any additional irrefutable markers in a subtree under a refutable identifier become redundant, correct?

Er, meant this:

Interpreted this way, any additional irrefutable markers in a subtree under an irrefutable identifier become redundant, correct?

For the proposal to use Nil for the expression semantics, yes.

You're right, this implies destructuring binding forms behave in a way that I flagged as possibly not wanted:

let {p?: {q: r}} = o;

would bind r to undefined for any o that doesn't have a p or that does

In my view it binds to Nil (but it is falsey, == null etc., typeof 'undefined' so it should work).

# Brendan Eich (6 years ago)

Herby Vojčík wrote:

Brendan Eich wrote:

Kevin Smith wrote:

Interpreted this way, any additional irrefutable markers in a subtree under a refutable identifier become redundant, correct?

Er, meant this:

Interpreted this way, any additional irrefutable markers in a subtree under an irrefutable identifier become redundant, correct?

For the proposal to use Nil for the expression semantics, yes.

You're right, this implies destructuring binding forms behave in a way that I flagged as possibly not wanted:

let {p?: {q: r}} = o;

would bind r to undefined for any o that doesn't have a p or that does

In my view it binds to Nil (but it is falsey, == null etc., typeof 'undefined' so it should work).

I don't think we should multiply risk by coupling destructuring (which is in ES6) to Nil (an unproposed strawman) at this point.

In theory and ignoring schedule and order of work, we could, and doing so has some symmetry (or really some duality) with a Nil-under-the-hood for ?. as existential operator. This is not a strong motivation in my view.

Also, would you really produce nil not undefined only for patterns where ? was used and the pattern irrefutably succeeded because of a missing property, and otherwise (no ?-pattern involved) bind r to undefined? IOW

let {p: {q?: r}} = {p: {s: 42}};

binds r to nil, while

let r = {p: {s: 42}}.r;

of course binds r to undefined? That seems undesirable.

If we agree to put nil in the language (ideally as a stdlib component, self-hosted or self-hostable), then we need to be careful about where it is observable. Just because one could use it under the hood to implement ?. does not mean it should be the result of ?-pattern default-matching.

# Herby Vojčík (6 years ago)

Brendan Eich wrote:

Herby Vojčík wrote:

That is, these are identical, except the syntax:

r = o.p.q {p: {q: r}} = o r = o.?p.q {?p: {q: r}} = o P=o.p; Q=o.?q {p: P, ?q: Q} = o

Here I part company only on syntax:

r = o?.p.q {p?: {q: r}} = o P=o.p; Q=o?.q {p: P, q?: Q} = o

And of course, the short-hand works:

p=o.p; q=o?.q {p, q?} = o

Oh, now that I see details, I do not like the syntax, because the symmetry of use of ? vanished.

Compare: r = o.?p.q {?p: {q: r}} = o // in both forms, "?p" vs, r = o?.p.q {p?: {q: r}} = o // 1st: "o?"; 2nd: "p?"

And: p=o.p; q=o.?q {p, ?q} = o // I can say informally // o.{p, ?q} meaning // {o.p, o.?q} vs. p=o.p; q=o?.q {p, q?} = o // 1st: "o?(.q)", 2nd: "(o.)q?"

But of coures, if it has some serious problems / is unacceptable from other reasons, then different syntax must be used.

# Brendan Eich (6 years ago)

Herby Vojčík wrote:

Brendan Eich wrote:

Herby Vojčík wrote:

That is, these are identical, except the syntax:

r = o.p.q {p: {q: r}} = o r = o.?p.q {?p: {q: r}} = o P=o.p; Q=o.?q {p: P, ?q: Q} = o

Here I part company only on syntax:

r = o?.p.q {p?: {q: r}} = o P=o.p; Q=o?.q {p: P, q?: Q} = o

And of course, the short-hand works:

p=o.p; q=o?.q {p, q?} = o

Oh, now that I see details, I do not like the syntax, because the symmetry of use of ? vanished.

Compare: r = o.?p.q {?p: {q: r}} = o // in both forms, "?p" vs, r = o?.p.q {p?: {q: r}} = o // 1st: "o?"; 2nd: "p?"

You're right, and in CoffeeScript o?.p tolerates null or undefined o.

Given eo.cs:

o = null o?.p o?.p.q

coffee -p eo.cs shows

(function() { var o;

o = null;

if (o != null) { o.p; }

if (o != null) { o.p.q; }

}).call(this);

So the ? in ?. really does modify its left operand.

It would be bad to diverge from CoffeeScript by standardizing some other ?. in ES7.

ISTM your equivalence was slightly broken -- but we can fix it:

r = o.p?.q <==> {p?: {q: r}} = o

To tolerate o = null and o = undefined (which is not what o.p?.q does), the equivalence would be:

r = o?.p.q <==> {p: {q: r}}? = o

I think this all works and makes sense. On the left, ? goes after the leading object or would-be object, denoted o. On the right, ? goes after the outermost object pattern.

Suffix vs. prefix should be an independent design decision, because ? as prefix should work the same on both sides, ergo, transposing around the operand to make ? a suffix should work the same on both sides.

# Brendan Eich (6 years ago)

Brendan Eich wrote:

To tolerate o = null and o = undefined (which is not what o.p?.q does), the equivalence would be:

I forgot to avoid the other slop, implicit ToObject conversion. Correcting:

"To tolerate primitive type value in o (which is not what o.p?.q does), the equivalence would be:"

# Brendan Eich (6 years ago)

Brendan Eich wrote:

Brendan Eich wrote:

To tolerate o = null and o = undefined (which is not what o.p?.q does), the equivalence would be:

I forgot to avoid the other slop, implicit ToObject conversion. Correcting:

"To tolerate primitive type value in o (which is not what o.p?.q does), the equivalence would be:"

Er, strike that -- not what CoffeeScript does, not wanted.

But this raises an issue: should ? applied to the whole object (o?.p in expression, {p:q}? = o in pattern language) also cause an implicit ToObject(o)?

# Herby Vojčík (6 years ago)

Brendan Eich wrote:

Herby Vojčík wrote:

Brendan Eich wrote:

Kevin Smith wrote:

Interpreted this way, any additional irrefutable markers in a subtree under a refutable identifier become redundant, correct?

Er, meant this:

Interpreted this way, any additional irrefutable markers in a subtree under an irrefutable identifier become redundant, correct?

For the proposal to use Nil for the expression semantics, yes.

You're right, this implies destructuring binding forms behave in a way that I flagged as possibly not wanted:

let {p?: {q: r}} = o;

would bind r to undefined for any o that doesn't have a p or that does

In my view it binds to Nil (but it is falsey, == null etc., typeof 'undefined' so it should work).

I don't think we should multiply risk by coupling destructuring (which is in ES6) to Nil (an unproposed strawman) at this point.

In theory and ignoring schedule and order of work, we could, and doing so has some symmetry (or really some duality) with a Nil-under-the-hood for ?. as existential operator. This is not a strong motivation in my view.

Also, would you really produce nil not undefined only for patterns where ? was used and the pattern irrefutably succeeded because of a missing property, and otherwise (no ?-pattern involved) bind r to undefined? IOW

let {p: {q?: r}} = {p: {s: 42}};

binds r to nil, while

let r = {p: {s: 42}}.r;

You meant let r = {p: {s: 42}}.p.q?, didn't you? This binds r to nil as well.

of course binds r to undefined? That seems undesirable.

Yes. But one of the problems mentioned on this thread was ARB's "It's not composable".

With internal-nil-exposed-undefined, these are different:

(p.q?).r
p.q?.r

With nil-reified, those are identical.

# Brendan Eich (6 years ago)

Herby Vojčík wrote:

Brendan Eich wrote:

Herby Vojčík wrote:

Brendan Eich wrote:

Kevin Smith wrote:

Interpreted this way, any additional irrefutable markers in a subtree under a refutable identifier become redundant, correct?

Er, meant this:

Interpreted this way, any additional irrefutable markers in a subtree under an irrefutable identifier become redundant, correct?

For the proposal to use Nil for the expression semantics, yes.

You're right, this implies destructuring binding forms behave in a way that I flagged as possibly not wanted:

let {p?: {q: r}} = o;

would bind r to undefined for any o that doesn't have a p or that does

In my view it binds to Nil (but it is falsey, == null etc., typeof 'undefined' so it should work).

I don't think we should multiply risk by coupling destructuring (which is in ES6) to Nil (an unproposed strawman) at this point.

In theory and ignoring schedule and order of work, we could, and doing so has some symmetry (or really some duality) with a Nil-under-the-hood for ?. as existential operator. This is not a strong motivation in my view.

Also, would you really produce nil not undefined only for patterns where ? was used and the pattern irrefutably succeeded because of a missing property, and otherwise (no ?-pattern involved) bind r to undefined? IOW

let {p: {q?: r}} = {p: {s: 42}};

binds r to nil, while

let r = {p: {s: 42}}.r;

You meant let r = {p: {s: 42}}.p.q?, didn't you?

Er, yes! Sorry about that.

This binds r to nil as well.

Confusion. Let me write it out to be sure we are talking about the same thing:

let r = {p: {s: 42}}.p.q;

binds nil to r? That's not backward compatible.

of course binds r to undefined? That seems undesirable.

Yes. But one of the problems mentioned on this thread was ARB's "It's not composable".

s/composable/compositional/

That was about CoffeeScript's semantics based on transcompilation, which I showed a few messages back. From Andreas's comments as captured in the minutes:

"""

ARB: This is non-compositional

     o = {}
     r = o?.p.q.r
     r = (o?.p).q.r
     r = o?.p.q.r()

Results in…

     var o, r;
     o = {};
     r = o != null ? o.p.q.r : void 0;
     r = (o != null ? o.p : void 0).q.r;
     r = o != null ? o.p.q.r() : void 0;

Non-starter.

"""

With internal-nil-exposed-undefined, these are different:

(p.q?).r
p.q?.r

With nil-reified, those are identical.

Yes, I already agreed (three times :-|) that nil rescues ?. from the condemnation ARB heaped on the CoffeeScript semantics.

That's not relevant to what we were just arguing about though: whether nil rather than undefined should be an observable result of either destructuring or (you seemed to say just above) property gets on plain old objects!

# Kevin Smith (6 years ago)

You're right, this implies destructuring binding forms behave in a way that I flagged as possibly not wanted:

let {p?: {q: r}} = o;

would bind r to undefined for any o that doesn't have a p or that does but o.p doesn't have a q.

So as Nil cascades downward, it essentially converts the whole subtree to a "deeply" irrefutable pattern.

On second look this is not as bad as I thought. It would be bad if r were not bound (so an outer r could become visible) depending on o's dynamics. That seems right out!

Yes, let's lobbest thy holy hand grenade at that one.

# Brendan Eich (6 years ago)

Brendan Eich wrote:

Yes, I already agreed (three times :-|) that nil rescues ?. from the condemnation ARB heaped on the CoffeeScript semantics.

That's not relevant to what we were just arguing about though: whether nil rather than undefined should be an observable result of either destructuring or (you seemed to say just above) property gets on plain old objects!

Apologies, I did not make the full idea clear:

You want nil in the language and observable via desugaring

o.?p.q ==> (o.p == null ? nil : o.p).q

without re-evaluating o.p of course (and using the syntax you showed). Furthermore, in the case where o.p == null, the result is nil.q which is nil.

(Please correct me if I am wrong on any of this! Appreciate the discussion here.)

What I would like to do: spec ?. without exposing nil. Perhaps this is not possible but I think it is, since we can make the definite semantics for o.?p.q or whatever syntax we want (I advocate o.p?.q) do a final censoring act that converts nil back to undefined.

You could be right that we want nil and ?. together -- no way to decouple risk. I'm not seeing that argument yet. People want ?. yesterday, nil not so much (see twitter).

# Brendan Eich (6 years ago)

Kevin Smith wrote:

You're right, this implies destructuring binding forms behave in a
way that I flagged as possibly not wanted:

  let {p?: {q: r}} = o;

would bind r to undefined for any o that doesn't have a p or that
does but o.p doesn't have a q. 

So as Nil cascades downward, it essentially converts the whole subtree to a "deeply" irrefutable pattern.

I would rather recast this into the expression language from the pattern language before invoking most-holy Nil :-P.

console.log({p: {q: 42}}.p?.q);

logs 42, while

console.log({p: {s: 42}}.p?.q);

would log undefined which could be nil in disguise, or not (see my previous post to this one).

Destructuring with the opt-in irrefutable syntax, suffix-? is my preference, does not seem to me to involve Nil in its explanation or semantics.

Sorry to be a stickler. I do not want to couple destructuring to exposed Nil.

On second look this is not as bad as I thought. It would be bad if
r were not bound (so an outer r could become visible) depending on
o's dynamics. That seems right out!

Yes, let's lobbest thy holy hand grenade at that one.

:-)

# Herby Vojčík (6 years ago)

Brendan Eich wrote:

Herby Vojčík wrote:

Brendan Eich wrote:

Herby Vojčík wrote:

Brendan Eich wrote:

You're right, this implies destructuring binding forms behave in a way that I flagged as possibly not wanted:

let {p?: {q: r}} = o;

would bind r to undefined for any o that doesn't have a p or that does

In my view it binds to Nil (but it is falsey, == null etc., typeof 'undefined' so it should work).

I don't think we should multiply risk by coupling destructuring (which is in ES6) to Nil (an unproposed strawman) at this point.

In theory and ignoring schedule and order of work, we could, and doing so has some symmetry (or really some duality) with a Nil-under-the-hood for ?. as existential operator. This is not a strong motivation in my view.

Also, would you really produce nil not undefined only for patterns where ? was used and the pattern irrefutably succeeded because of a missing property, and otherwise (no ?-pattern involved) bind r to undefined? IOW

let {p: {q?: r}} = {p: {s: 42}};

binds r to nil, while

let r = {p: {s: 42}}.r;

You meant let r = {p: {s: 42}}.p.q?, didn't you?

Er, yes! Sorry about that.

This binds r to nil as well.

Confusion. Let me write it out to be sure we are talking about the same thing:

let r = {p: {s: 42}}.p.q; binds nil to r? That's not backward compatible.

Sorry, I did wrote it so it could lead to confusion. Let me try to explain better.

(1)	let {p: {q?: r}} = {p: {s: 42}};
	// me: r = Nil, you: ???, probably undefined

(2)	let r = {p: {s: 42}}.p.q?;
	// me: r = Nil, you: ???, probably undefined

(3)	let {p: {q: r}} = {p: {s: 42}};
	// r = undefined (of course)

(4)	let r = {p: {s: 42}}.p.q;
	// r = undefined (of course)

You compared (1) to (4). I would it is comparing apples to oranges, and that (1) and (2) or (3) and (4) should be compared.

Yes. But one of the problems mentioned on this thread was ARB's "It's not composable".

s/composable/compositional/

That was about CoffeeScript's semantics based on transcompilation, which I showed a few messages back. From Andreas's comments as captured in the minutes:

"""

ARB: This is non-compositional

o = {} r = o?.p.q.r r = (o?.p).q.r r = o?.p.q.r()

Results in…

var o, r; o = {}; r = o != null ? o.p.q.r : void 0; r = (o != null ? o.p : void 0).q.r; r = o != null ? o.p.q.r() : void 0;

Non-starter.

"""

With internal-nil-exposed-undefined, these are different:

(p.q?).r p.q?.r

With nil-reified, those are identical.

Yes, I already agreed (three times :-|) that nil rescues ?. from the condemnation ARB heaped on the CoffeeScript semantics.

Sorry, I wasn't after this. It just seems there are still some misunderstadings...

That's not relevant to what we were just arguing about though: whether nil rather than undefined should be an observable result of either

Now I am confused. For (p.q?).r to be same as p.q?.r, (p.q?) must return reified Nil, not undefined. I was at the impression you say "Nil at the background, but whenever it becomes observable, it should be changed to undefined". That means p.q? returns undefined, and (p.q?).r fails.

Or do you mean (1) and (2) from above are not to be equivalant ((1) Nil, (2) undefined)?

Or something completely different?

destructuring or (you seemed to say just above) property gets on plain old objects!

No, that was a misunderstanding.

# Herby Vojčík (6 years ago)

Brendan Eich wrote:

Brendan Eich wrote:

Yes, I already agreed (three times :-|) that nil rescues ?. from the condemnation ARB heaped on the CoffeeScript semantics.

That's not relevant to what we were just arguing about though: whether nil rather than undefined should be an observable result of either destructuring or (you seemed to say just above) property gets on plain old objects!

Apologies, I did not make the full idea clear:

You want nil in the language and observable via desugaring

o.?p.q ==> (o.p == null ? nil : o.p).q

without re-evaluating o.p of course (and using the syntax you showed). Furthermore, in the case where o.p == null, the result is nil.q which is nil.

(Please correct me if I am wrong on any of this! Appreciate the discussion here.)

No, now it is exactly how it was meant.

# Brendan Eich (6 years ago)

Herby Vojčík wrote:

In the short example you sketched, with case {must, ?may} there is no need to diverge from the destructuring; so I did not even imagine such divergence for the moment.

Should a proxy in the head of a match be able to observe case-by-case refutation?

We need to detail how Nil works, how it cannot be wrapped or observed, etc. in order to maintain equivalence.

In my naive view, [[GetP]] returns Nil, [[SetP]] does nothing, [[Call]] return Nil. But there are sure some nasty details down there.

Yeah, this is unsafe by design, if the spec has a bug then Nil leaks out. Want undefined in ES6, not Nil.

# Brendan Eich (6 years ago)

[Trimming overcited top text.]

Herby Vojčík wrote:

Confusion. Let me write it out to be sure we are talking about the same thing:

let r = {p: {s: 42}}.p.q; binds nil to r? That's not backward compatible.

Sorry, I did wrote it so it could lead to confusion. Let me try to explain better.

(1)    let {p: {q?: r}} = {p: {s: 42}};
    // me: r = Nil, you: ???, probably undefined

Yes. For the reason I gave: destructuring in ES6 cannot depend on Nil, but I argue should include irrefutable opt-in via suffix-?.

(2)    let r = {p: {s: 42}}.p.q?;
    // me: r = Nil, you: ???, probably undefined

Yes, undefined. For the reason I gave, not coupling too much among ES7 or later straw proposals.

(3)    let {p: {q: r}} = {p: {s: 42}};
    // r = undefined (of course)

No issue, great.

(4)    let r = {p: {s: 42}}.p.q;
    // r = undefined (of course)

No issue, great. Whew!

You compared (1) to (4). I would it is comparing apples to oranges, and that (1) and (2) or (3) and (4) should be compared.

Apologies, on re-reading I see I misread your trailing '?' after 'q' in

You meant let r = {p: {s: 42}}.p.q?, didn't you?

That's not relevant to what we were just arguing about though: whether nil rather than undefined should be an observable result of either

Now I am confused. For (p.q?).r to be same as p.q?.r, (p.q?) must return reified Nil, not undefined. I was at the impression you say "Nil at the background, but whenever it becomes observable, it should be changed to undefined". That means p.q? returns undefined, and (p.q?).r fails.

Obviously I don't want these different parenthesizations to fail. I believe we can spec the semantics such that InternalNil, just like ES1-6's Reference type, is not observable.

# Herby Vojčík (6 years ago)

Brendan Eich wrote:

Herby Vojčík wrote:

In the short example you sketched, with case {must, ?may} there is no need to diverge from the destructuring; so I did not even imagine such divergence for the moment.

Should a proxy in the head of a match be able to observe case-by-case refutation?

I'd say no, it should be done transparently be the language itself, via that (foo==null?nil:foo) desugaring. But that's mostly a feeling (the question is, how to whether foo==null is done by some shortcut without actually getting foo, proxy may then observe that shortcut).

We need to detail how Nil works, how it cannot be wrapped or observed, etc. in order to maintain equivalence.

In my naive view, [[GetP]] returns Nil, [[SetP]] does nothing, [[Call]] return Nil. But there are sure some nasty details down there.

Yeah, this is unsafe by design, if the spec has a bug then Nil leaks out. Want undefined in ES6, not Nil.

I don't understand the unsafety. If Nil is observable part of the language, then this is natural semantics. If it should be hidden, that's another story.

# Brendan Eich (6 years ago)

Herby Vojčík wrote:

We need to detail how Nil works, how it cannot be wrapped or observed, etc. in order to maintain equivalence.

In my naive view, [[GetP]] returns Nil, [[SetP]] does nothing, [[Call]] return Nil. But there are sure some nasty details down there.

Yeah, this is unsafe by design, if the spec has a bug then Nil leaks out. Want undefined in ES6, not Nil.

I don't understand the unsafety. If Nil is observable part of the language, then this is natural semantics. If it should be hidden, that's another story.

I assumed from context (cited above) that you were talking about destructruing in ES6. That spec lacks Nil as an observable and must censor any internal Nil specification type. Could be done, but I argue it's safer to leave [[GetP]] etc. dealing in undefined for now.

Of course if we want Nil in the language, then full speed ahead! That would be later (ES7 or above), if ever.

# Kevin Smith (6 years ago)

The proposed behavior when a non-leaf is marked irrefutable (the entire subtree is essentially irrefutable) seems reasonable to. Unless I'm mistaken, it's also backward compatible with your original leaf-only design, right? If so, irrefutability could be leaf-only in ES6, to be expanded possibly in ES7 along with the existential operator.

# Kevin Smith (6 years ago)

The proposed behavior when a non-leaf is marked irrefutable (the entire

subtree is essentially irrefutable)

Sorry, should read:

The proposed behavior when a non-leaf is marked irrefutable (the entire subtree imputes undefined when triggered) ...

# Brendan Eich (6 years ago)

I think we can do this now. Allen should weigh in. Hope to hear from Andreas R. soon too!

Apologies for the long thread, and thanks to Herby for interaction that clarified many things. Perhaps I should resummarize:

The best new-new plan to avoid adding slop is to revise ES6 destructuring thus:

  1. No ToObject(RHS).
  2. Exception on missing property selected without a ?-suffix.
  3. ?-suffix allowed on any pattern, imputing undefined deeply instead of refuting. 4: the ? is a separate lexeme from the : in long-hand patterns.

How's that?

# Allen Wirfs-Brock (6 years ago)

On Jan 2, 2013, at 7:58 PM, Brendan Eich wrote:

I think we can do this now. Allen should weigh in. Hope to hear from Andreas R. soon too!

Apologies for the long thread, and thanks to Herby for interaction that clarified many things. Perhaps I should resummarize:

The best new-new plan to avoid adding slop is to revise ES6 destructuring thus:

  1. No ToObject(RHS).
  2. Exception on missing property selected without a ?-suffix.
  3. ?-suffix allowed on any pattern, imputing undefined deeply instead of refuting. 4: the ? is a separate lexeme from the : in long-hand patterns.

How's that?

I'm fine with points 2-4. However, I think no ToObject(RHS) would be a mistake. Here's why:

In almost all other situations where an object is needed, a primitive value (including a literal) can be used. This includes contexts that use the dot and [ ] property access operators. Essentially, in all object appropriate situations primitive values act as if they were objects. This is important in that in most cases it allows ES programmers to ignore distinctions between objects and primitive values.

Destructuring is frequently described as simply a de-sugaring over property access in assignments and declarations.

let {length: len} = obj;

is most easily explained by saying that it is equivalent to:

let len = obj.length;

But if the ToObject is eliminated from the RHS then this desugaring equivalence is no longer valid in all cases. The obj.length form would work fine if the value of obj was a string but the destructuring form will throw. This breaks the general ES rule that you can use a primitive value in any context where an object is required. It is the sort of contextual special case that developers hate and which makes a language harder to learn. Consistency is important.

Finally, note that now with exceptions on missing properties (without ?) it is likely that most situations where a primitive value is erroneously used on the RHS will throw anyway simply because the primitive wrapper probably won't have the requested property. So, removing the ToObject just creates inconsistency without adding much in the way of error detection.

# Nathan Wall (6 years ago)

Brendan Eich wrote:

Herby Vojčík wrote:

Now I am confused. For (p.q?).r to be same as p.q?.r, (p.q?) must return reified Nil, not undefined. I was at the impression you say "Nil at the background, but whenever it becomes observable, it should be changed to undefined". That means p.q? returns undefined, and (p.q?).r fails.

Obviously I don't want these different parenthesizations to fail. I believe we can spec the semantics such that InternalNil, just like ES1-6's Reference type, is not observable.

What happens in more complicated expressions?

let foo, bar = (foo = p.q?).r;

It would be strange to me if foo === undefined && bar === undefined. Herby's proposal that foo === nil && bar === nil makes more sense.

I think it would be expected for bar = (foo = p.q?).r to result in the same value for bar as bar = (p.q?).r.

Nathan

# Brendan Eich (6 years ago)

Nathan Wall wrote:

Brendan Eich wrote:

Herby Vojčík wrote:

Now I am confused. For (p.q?).r to be same as p.q?.r, (p.q?) must return reified Nil, not undefined. I was at the impression you say "Nil at the background, but whenever it becomes observable, it should be changed to undefined". That means p.q? returns undefined, and (p.q?).r fails. Obviously I don't want these different parenthesizations to fail. I believe we can spec the semantics such that InternalNil, just like ES1-6's Reference type, is not observable.

What happens in more complicated expressions?

 let foo, bar = (foo = p.q?).r;

That depends, among other things, on whether ?. is one new lexeme or two!

IOW, you shouldn't assume that ?. in the expression language means ? (suffix-?) is in the expression language.

Of course, we could do all three of nil, ? and ?. as Herby wants. That is ES7 for sure and it ties things up in a bow. But is nil really worth it, never mind suffix-? in expressions?

It would be strange to me if foo === undefined&& bar === undefined. Herby's proposal that foo === nil&& bar === nil makes more sense.

Given nil and the ability to write suffix-? inside parens, and dot after, I agree.

Not sold on tying ?. to -? and nil yet, tho!

# Kevin Smith (6 years ago)

I wonder: in what way does this design effectively decide the design for an existential member operator (?.)?

If it does decide the matter, then it seems like it might as well go into ES6.

# Brendan Eich (6 years ago)

Kevin Smith wrote:

I wonder: in what way does this design effectively decide the design for an existential member operator (?.)?

Adding suffix-? to the pattern language still leaves open some design decisions:

A. Whether to support suffix-? in expressions. B. If not, whether to support ?. as a single lexeme for a saner existential operator, with censored internal-Nil. C. If so, then ? and . compose, so: whether to expose Nil in the language.

I agree that given "yes" to A, ?. must be two lexemes and operators, one suffix-? and the other good ol' dot. But this does not imply "yes" to C.

I admit, it's cleaner and more CoffeeScript-friendly to say A="yes". That still could leave C="no", since of course (same runtime semantics as JS) CoffeeScript says "no" to C.

If it does decide the matter, then it seems like it might as well go into ES6.

That's not true. It's still more work to spec ?. as well as refutable destructuring for ES6. It requires an internal Nil (or Reference variation as Allen suggested to me). It requires grammar hassling to allow suffix-? in expressions.

# Kevin Smith (6 years ago)

That's not true. It's still more work to spec ?. as well as refutable destructuring for ES6. It requires an internal Nil (or Reference variation as Allen suggested to me). It requires grammar hassling to allow suffix-? in expressions.

I see that. Bear with me nonetheless.

It seems that on both the pattern side and the expression side, we're trying to express the same thing: an "irrefutable property get", where we get a nillish reference if the property does not exist. Moreover, I assert that this is exactly what we want on the expression side, nothing more or less(*).

We know that we're going to want to expose this "irrefutable property get" on both the pattern side and the expression side at some point. We also know that we want to use the "?" sigil to indicate this operation.

The syntax question is whether we use suffix-? (where the "?" appears after the property name) or prefix-? (where the "?" appears before the property name).

(Prefix-? on the expression side means that ".?" is a single lexeme.)

Using suffix-? on the expression side is not backward compatible for the ASI reasons already mentioned:

a.b?
c:
d;

So we're down to two possibilities:

  1. Expression: prefix-?, Pattern: suffix-?
  2. Expression: prefix-? Pattern: prefix-?

Consistency would seem to dictate (1).

{ p: { ?q: v } } = o;
v = o.p.?q;

But in any case, the "irrefutable property get" operation has to be specified, so given my assertion above (*) it boils down to just deciding this prefix or suffix question.

# Brendan Eich (6 years ago)

Kevin Smith wrote:

Using suffix-? on the expression side is not backward compatible for the ASI reasons already mentioned:

a.b?
c:
d;

I raised this problem-case, so I want to point out that we could take other courses:

  • Reckon that labels are rare and this won't bite, so let it stand, just as

a = b (c)

is a hazard today -- and one that bites much more.

  • Don't allow suffix-? to be followed by a newline.

So we're down to two possibilities:

  1. Expression: prefix-?, Pattern: suffix-?
  2. Expression: prefix-? Pattern: prefix-?

Consistency would seem to dictate (1).

{ p: { ?q: v } } = o;
v = o.p.?q;

You must have meant (2).

Apart from deviating from the cowpath (CoffeeScript), prefix-? is equivalent to suffix-? as I argued in reply to Herby. We'd want to support

let ?{p: v} = o; let v = ?o.p;

to handle the case of undefined or null 'o', of course, in which case v would be initialized to undefined.

Stopping here to make sure we are in sync.

# Kevin Smith (6 years ago)

I raised this problem-case, so I want to point out that we could take other courses:

  • Reckon that labels are rare and this won't bite, so let it stand, just as

a = b (c)

is a hazard today -- and one that bites much more.

  • Don't allow suffix-? to be followed by a newline.

Leaving aside ASI for a moment, there are other issues:

let v = obj?+(0+1+2+3+4+5+6+7+8+9+...+n):null;

We don't know whether this is a conditional expression or not until we get to the ":" an arbitrary distance away from the "?". We might be able to use another cover grammar approach here, but is it worth it?

You must have meant (2).

Yep!

Apart from deviating from the cowpath (CoffeeScript), prefix-? is equivalent to suffix-? as I argued in reply to Herby. We'd want to support

let ?{p: v} = o; let v = ?o.p;

to handle the case of undefined or null 'o', of course, in which case v would be initialized to undefined.

Would we? This is something a little different than "irrefutable property get". Thinking...

# Herby Vojčík (6 years ago)

Kevin Smith wrote:

I raised this problem-case, so I want to point out that we could
take other courses:

* Reckon that labels are rare and this won't bite, so let it stand,
just as

a = b
(c)

is a hazard today -- and one that bites much more.

* Don't allow suffix-? to be followed by a newline.

Leaving aside ASI for a moment, there are other issues:

 let v = obj?+(0+1+2+3+4+5+6+7+8+9+...+n):null;

Is it bad to specify that unless it is at the end of (sub)Expression or before dot, '?' is treated as first ? in ?: ? Then you have to explicitly parenthesize it and that's all.

# Kevin Smith (6 years ago)

Is it bad to specify that unless it is at the end of (sub)Expression or before dot, '?' is treated as first ? in ?: ? Then you have to explicitly parenthesize it and that's all.

How do you specify "end of expression", though? You have to consider ASI here as well. Is the complexity you suggest worth it?

# Herby Vojčík (6 years ago)

Kevin Smith wrote:

Is it bad to specify that unless it is at the end of (sub)Expression
or before dot, '?' is treated as first ? in ?: ? Then you have to
explicitly parenthesize it and that's all.

How do you specify "end of expression", though? You have to consider ASI here as well. Is the complexity you suggest worth it?

ASI here as well :-/ hm, yes. It is complicated.

The only solution I see now is abandon deatiled grammar solution, move to higher point of view, treat ? always as ?:, and if the next thing you see is a dot or end of subexpression (by any means), make it "existential ?".

It is probably what you are saying by "not worth the complexity".

# Kevin Smith (6 years ago)

Would we? This is something a little different than "irrefutable property get". Thinking...

Notice that, strictly speaking, we don't need an "irrefutable object" operation:

let v = ?o.p;

// is equivalent to:
let v = {o}.?o.p;

The allocation in "{o}" can be statically optimized away.

The same goes for the pattern language:

let ?{ p: v } = o;

// is equivalent to:
let { ?o: { p: v } } = {o};
# Brendan Eich (6 years ago)

Kevin Smith wrote:

I raised this problem-case, so I want to point out that we could
take other courses:

* Reckon that labels are rare and this won't bite, so let it
stand, just as

a = b
(c)

is a hazard today -- and one that bites much more.

* Don't allow suffix-? to be followed by a newline.

Leaving aside ASI for a moment, there are other issues:

let v = obj?+(0+1+2+3+4+5+6+7+8+9+...+n):null;

We don't know whether this is a conditional expression or not until we get to the ":" an arbitrary distance away from the "?".

Yes, this is a good point if obj? could be an expression on the left of binary +. The same problem arises with - / [ and ( of course -- just as with lack-of-ASI.

We might be able to use another cover grammar approach here, but is it worth it?

We'd rather use a lookahead restriction forbidding the token after ? from being in {+,-,/,[,(} -- and possibly from being a LineTerminator, although that seems silly to me on reflection.

Apart from deviating from the cowpath (CoffeeScript), prefix-? is
equivalent to suffix-? as I argued in reply to Herby. We'd want to
support

  let ?{p: v} = o;
  let v = ?o.p;

to handle the case of undefined or null 'o', of course, in which
case v would be initialized to undefined.

Would we? This is something a little different than "irrefutable property get". Thinking...

The suffix-? operator works on identifier expressions too. Those happen to irrefutably throw just fine (throw ReferenceError) in JS, unlike MemberExpressions such as foo.bar where !('bar' in foo).

# Brendan Eich (6 years ago)

Kevin Smith wrote:

Would we?  This is something a little different than "irrefutable
property get".  Thinking...

Notice that, strictly speaking, we don't need an "irrefutable object" operation:

let v = ?o.p;

// is equivalent to:
let v = {o}.?o.p;

The allocation in "{o}" can be statically optimized away.

The same goes for the pattern language:

let ?{ p: v } = o;

// is equivalent to:
let { ?o: { p: v } } = {o};

Sure, but that's crying out for a shorter form that avoids repeating o. Note also that in the expression language, |o| might be a longer expression (even, gasp, with embedded effects) that you would not want to repeat.

Whatever we do for existential operators, destructuring in ES6 with refutable-by-default patterns wants something at all levels, including outermost -- or so I argue.

# Kevin Smith (6 years ago)

Sure, but that's crying out for a shorter form that avoids repeating o. Note also that in the expression language, |o| might be a longer expression (even, gasp, with embedded effects) that you would not want to repeat.

I see. I think overloading the lone "?" is grammatically sketchy, though...

Whatever we do for existential operators, destructuring in ES6 with refutable-by-default patterns wants something at all levels, including outermost -- or so I argue.

Maybe so. Again, overloading a lone "?" outside of the pattern itself makes me feel itchy.

Retiring back to the thinking cave for now...

# Herby Vojčík (6 years ago)

Brendan Eich wrote:

Herby Vojčík wrote:

We need to detail how Nil works, how it cannot be wrapped or observed, etc. in order to maintain equivalence.

In my naive view, [[GetP]] returns Nil, [[SetP]] does nothing, [[Call]] return Nil. But there are sure some nasty details down there.

Yeah, this is unsafe by design, if the spec has a bug then Nil leaks out. Want undefined in ES6, not Nil.

I don't understand the unsafety. If Nil is observable part of the language, then this is natural semantics. If it should be hidden, that's another story.

I assumed from context (cited above) that you were talking about destructruing in ES6. That spec lacks Nil as an observable and must censor any internal Nil specification type. Could be done, but I argue it's safer to leave [[GetP]] etc. dealing in undefined for now.

Of course if we want Nil in the language, then full speed ahead! That would be later (ES7 or above), if ever.

Well, the objection that reified nil hides errors is valid; it bugged me, too. I pondered how it can be solved, or if it will be obstacle big enough.

In the light of this I think nil-underneath-resurfaced-as-undefined is good strategy, things like (foo=o.p?).q must be augmentede a bit more: (foo=o.p?)?.q

I think it is a problem only for existential operator, destructuring has no subexpressions.

# Herby Vojčík (6 years ago)

Herby Vojčík wrote:

In the light of this I think nil-underneath-resurfaced-as-undefined is good strategy, things like (foo=o.p?).q must be augmentede a bit more: (foo=o.p?)?.q

Well, I meant this, the previous example is a bit strange: (foo=o?.p).q must be (foo=o?.p)?.q

# Kevin Smith (6 years ago)

I was worried that a suffix "?" in the pattern language might be future-hostile to an existential operator or existential member operator, owing to the fact that suffix "?" is grammatically a bit sketchy on the expression side (in my mind, anyway).

But I think I'm satisfied now. Basically, I don't think that an existential operator is all that important to have, because:

  1. Pattern matching via a match statement or expression is far more powerful, and would subsume much of the use case for an existential operator.

  2. It may be that what we really want for the non-match use case isn't an existential operator, but something more like a unary "try path" operator. To extend the example from the CoffeeScript docs, something along these lines:

    let zip = select lottery.draw().winners[0].address.zipcode;

Where "select" is a context-sensitive keyword and the select expression modifies the behavior of its child member expressions such that instead of throwing TypeErrors when the object is null or undefined, they evaluate to a "nillish" reference.

Just a thought...

# Andreas Rossberg (6 years ago)

On Jan 2, 2013, at 7:58 PM, Brendan Eich wrote:

I think we can do this now. Allen should weigh in. Hope to hear from Andreas R. soon too!

Apologies for the long thread, and thanks to Herby for interaction that clarified many things. Perhaps I should resummarize:

The best new-new plan to avoid adding slop is to revise ES6 destructuring thus:

  1. No ToObject(RHS).
  2. Exception on missing property selected without a ?-suffix.
  3. ?-suffix allowed on any pattern, imputing undefined deeply instead of refuting. 4: the ? is a separate lexeme from the : in long-hand patterns.

How's that?

[Sorry to be late, catching up from a 2 weeks off-line vacation. :) ]

Thanks Brendan for reviving the discussion. That plan mostly matches what I was arguing for last time round (including the necessity to allow ? on every pattern), so me likes. I still see some issues with making ? postfix (readability, parsing), but that's a comparably minor point.

On 4 January 2013 01:33, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

I'm fine with points 2-4. However, I think no ToObject(RHS) would be a mistake. Here's why:

In almost all other situations where an object is needed, a primitive value (including a literal) can be used. This includes contexts that use the dot and [ ] property access operators. Essentially, in all object appropriate situations primitive values act as if they were objects. This is important in that in most cases it allows ES programmers to ignore distinctions between objects and primitive values.

Destructuring is frequently described as simply a de-sugaring over property access in assignments and declarations.

let {length: len} = obj;

is most easily explained by saying that it is equivalent to:

let len = obj.length;

But if the ToObject is eliminated from the RHS then this desugaring equivalence is no longer valid in all cases. The obj.length form would work fine if the value of obj was a string but the destructuring form will throw. This breaks the general ES rule that you can use a primitive value in any context where an object is required. It is the sort of contextual special case that developers hate and which makes a language harder to learn. Consistency is important.

Finally, note that now with exceptions on missing properties (without ?) it is likely that most situations where a primitive value is erroneously used on the RHS will throw anyway simply because the primitive wrapper probably won't have the requested property. So, removing the ToObject just creates inconsistency without adding much in the way of error detection.

All good points, and I think it makes sense to separate the discussion of implicit conversion from refutability itself.

I think your argument presupposes an assumption that only happens to hold for the pattern language currently proposed, but is unlikely to remain so in the future: namely, that all patterns describe objects. In particular, a pattern matching construct wants to allow, say, strings, null, true and others as patterns, and surely you do not want ToObject in those cases.

One defensible position might be to only invoke ToObject when actually matching against an object/array pattern. But to be consistent, you'd then have to do similar conversions for other patterns, too, e.g. invoking ToString when matching against a string. Unfortunately, that would make using a future pattern matching construct correctly much harder and more tedious. For example, writing

match (e) { case true: ... case null: ... case {x, y}: ... }

will take the first case for all objects, although that is unlikely to be the intention, here or elsewhere. Similarly,

match (e) { case {}: ... case }

runs into the first case for almost anything when the more natural expectation may be only actual objects.

So I believe that in the context of patterns, implicit conversions violate the principle of least surprise and are future-hostile (in terms of usability) towards a more general pattern matching facility.