fail-fast object destructuring

# David Herman (6 years ago)

Implicit coercions suck, and IMO it would be better if destructuring didn't add new ones to JS. In the current draft spec, I believe if you match an object destructuring pattern against a non-object or an object that doesn't have one or more of the properties, it quietly proceeds -- masking what is quite likely an error:

var { foo, bar } = getFooAndBar();

If getFooAndBar() doesn't produce an object with foo and bar properties, there's probably something going wrong! And most of the time when you do want to be forgiving (say, in a function signature, or when reading a configuration file), you have a specific default value you want to provide, which can be specified with the default syntax:

var { foo = defaultFooValue, bar = defaultBarValue } = getFooAndBar();

I'd like to propose the following changes:

(a) Throw when matching null, undefined, booleans, or strings against an object pattern. (I don't propose throwing when matching a string against an array pattern, however, since strings behave structurally like read-only arrays.)

(b) Allow a shorthand "?" prefix for properties in an object destructuring pattern. This is simply shorthand for defaulting to undefined. IOW, the following are all equivalent:

var { ?foo } = getFoo();
var { foo = undefined } = getFoo();
var { foo: foo = undefined } = getFoo();
var { ?foo: foo } = getFoo();

Note, however, that (b) is not strictly necessary to get the fail-soft behavior. You can always write the explicit default. But it makes it considerably more convenient to get the fail-soft behavior when you want it -- only one character more expensive than fail-fast.

# Andreas Rossberg (6 years ago)

On 25 June 2012 17:17, David Herman <dherman at mozilla.com> wrote:

Implicit coercions suck, and IMO it would be better if destructuring didn't add new ones to JS. In the current draft spec, I believe if you match an object destructuring pattern against a non-object or an object that doesn't have one or more of the properties, it quietly proceeds -- masking what is quite likely an error:

var { foo, bar } = getFooAndBar();

If getFooAndBar() doesn't produce an object with foo and bar properties, there's probably something going wrong! And most of the time when you do want to be forgiving (say, in a function signature, or when reading a configuration file), you have a specific default value you want to provide, which can be specified with the default syntax:

var { foo = defaultFooValue, bar = defaultBarValue } = getFooAndBar();

I'd like to propose the following changes:

(a) Throw when matching null, undefined, booleans, or strings against an object pattern. (I don't propose throwing when matching a string against an array pattern, however, since strings behave structurally like read-only arrays.)

(b) Allow a shorthand "?" prefix for properties in an object destructuring pattern. This is simply shorthand for defaulting to undefined. IOW, the following are all equivalent:

var { ?foo } = getFoo();    var { foo = undefined } = getFoo();    var { foo: foo = undefined } = getFoo();    var { ?foo: foo } = getFoo();

Note, however, that (b) is not strictly necessary to get the fail-soft behavior. You can always write the explicit default. But it makes it considerably more convenient to get the fail-soft behavior when you want it -- only one character more expensive than fail-fast.

+10^20, although you don't say that "let {foo} = {}" should throw as well -- but your (b) doesn't seem to be motivated without proposing that.

# Brendan Eich (6 years ago)

Andreas Rossberg wrote:

On 25 June 2012 17:17, David Herman<dherman at mozilla.com> wrote:

Implicit coercions suck, and IMO it would be better if destructuring didn't add new ones to JS. In the current draft spec, I believe if you match an object destructuring pattern against a non-object or an object that doesn't have one or more of the properties, it quietly proceeds -- masking what is quite likely an error:

var { foo, bar } = getFooAndBar();

If getFooAndBar() doesn't produce an object with foo and bar properties, there's probably something going wrong! And most of the time when you do want to be forgiving (say, in a function signature, or when reading a configuration file), you have a specific default value you want to provide, which can be specified with the default syntax:

var { foo = defaultFooValue, bar = defaultBarValue } = getFooAndBar();

I'd like to propose the following changes:

(a) Throw when matching null, undefined, booleans, or strings against an object pattern. (I don't propose throwing when matching a string against an array pattern, however, since strings behave structurally like read-only arrays.)

(b) Allow a shorthand "?" prefix for properties in an object destructuring pattern. This is simply shorthand for defaulting to undefined. IOW, the following are all equivalent:

var { ?foo } = getFoo();
var { foo = undefined } = getFoo();
var { foo: foo = undefined } = getFoo();
var { ?foo: foo } = getFoo();

Note, however, that (b) is not strictly necessary to get the fail-soft behavior. You can always write the explicit default. But it makes it considerably more convenient to get the fail-soft behavior when you want it -- only one character more expensive than fail-fast.

+10^20, although you don't say that "let {foo} = {}" should throw as well -- but your (b) doesn't seem to be motivated without proposing that.

Dave did write "If getFooAndBar() doesn't produce an object with foo and bar properties, there's probably something going wrong!"

The test would be "in" not "own", to be clear, so if Object.prototype.foo = 42 had been done previously, let {foo} = {} would destructure 42 as the initial value of the foo let binding.

I'm ok with this even though it is not compatible with what we've implemented and user-tested for almost six years, but I want (b) as part of the deal. We have code that will use ?-prefixing.

# Erik Arvidsson (6 years ago)

On Mon, Jun 25, 2012 at 9:14 AM, Brendan Eich <brendan at mozilla.org> wrote:

I'm ok with this even though it is not compatible with what we've implemented and user-tested for almost six years, but I want (b) as part of the deal. We have code that will use ?-prefixing.

I want this (a) too but I don't think (b) is worth the syntax. Defaulting to undefined is a better pattern and it encourages people to default to what they want instead of adding one more line of code.

With (b) I can see code like this:

var {?a} = someObject; if (typeof a === 'undefined') a = defaultValue;

Clearly, an anti pattern over

var {a = defaultValue} = someObject;

# Allen Wirfs-Brock (6 years ago)

On Jun 25, 2012, at 8:17 AM, David Herman wrote:

Implicit coercions suck, and IMO it would be better if destructuring didn't add new ones to JS. In the current draft spec, I believe if you match an object destructuring pattern against a non-object or an object that doesn't have one or more of the properties, it quietly proceeds -- masking what is quite likely an error:

What it currently does, its

   if value is neither null or undefined, then
         Let obj be ToObject(value)
   Else, let obj be undefined

obj is then the value that is destructured. This happens recursively at each level of the de-structuring.

There are really three separable issues that Dave talks about: 1) The ToObject conversion shown above 2) The handling of null/undefined shown above. 3) The handler of missing properties

  1. ToObject conversion is used pervasively throughout the ES semantics to coerce primitive values to object values. It is an important part of the illusion that for most purposes Number, String, and Boolean primitive values can be treated as-if they were objects.

In particular, I don't see any particular reason why it isn't perfectly reasonable to say things like:

let {concat, indexOf, lastIndexOf} = ""; //get some string methods let {length,10:char10} = "this is a long string and I don't want to count chars";

let {toLocaleSring: oldToLocale} = 1.0;

There is a tension between fail-fast and the internal consistency of the language. In the case of primitive value conversion I don't think we should deviate from the existing consistent behavior of the language semantics. It just make it much more difficult for a programmer to develop a conceptual model of how the language works, short of memorizing all the special cases. What is the explanation for why: let oldToLocale = 1.0.toLocaleString; would work, yet let {toLocaleSring: oldToLocale} = 1.0; doesn't. The latter feels like somebody made an arbitrary change to the general rules that the language follows.

  1. Is certainly debatable as it is a deviation from the ToObject rules that are applied in most other contexts of the language. I believe it is currently specified that way because that is what Firefox did. I don't have any particular issue eliminating that deviation and applying the standard ToObject rules which means that:" let {a,b} =- null; would throw.

  2. Again we have a internal consistency issue:

    let myFoo = {a:0,b:1}.foo; vs let {foo: myFoo} = {a:0,b:1};

why should one work and the other fail? The general rule of JS is that accessing a missing property returns undefined. Whether or not you think it is a good rule, it's the way the core or the language works and it can't change. It's also an easy rule for anybody to learn. Making exceptions to that rule just makes the language less internally consistent, harder to learn, and more complex. I don't think we should do it.

var { foo, bar } = getFooAndBar();

If getFooAndBar() doesn't produce an object with foo and bar properties, there's probably something going wrong! And most of the time when you do want to be forgiving (say, in a function signature, or when reading a configuration file), you have a specific default value you want to provide, which can be specified with the default syntax:

var { foo = defaultFooValue, bar = defaultBarValue } = getFooAndBar();

I'd like to propose the following changes:

(a) Throw when matching null, undefined, booleans, or strings against an object pattern. (I don't propose throwing when matching a string against an array pattern, however, since strings behave structurally like read-only arrays.)

String values should behave consistently in all situations. What's wrong with: let {3:char3, length} = str;

(b) Allow a shorthand "?" prefix for properties in an object destructuring pattern. This is simply shorthand for defaulting to undefined. IOW, the following are all equivalent:

var { ?foo } = getFoo(); var { foo = undefined } = getFoo(); var { foo: foo = undefined } = getFoo(); var { ?foo: foo } = getFoo();

But var ?foo = getFoo(); would be syntactically illegal!

If we really wanted to address this problem, I think a better approach that would have internal consistency would be to allow a ? before a binding identifer meaning throw if undefiled.

let foo = getFoo(); //assigns undefined if that is what is return from getFoo() let ?foo = getFoo(); //Throws if getFoo() returns undefined. let {foo} = getFoo(); //initialized foo to undefined if obj returned from getFoo() does not have a foo property or its value is undefined let {?foo} = getFoo(); //Throws if obj returned from getFoo() does not have a foo property or its value is undefined

I think this would be a better approach. I'm not particularly excited about including it in ES6 without significantly more thought. It would always be added latter.

# Brendan Eich (6 years ago)

Allen Wirfs-Brock wrote:

On Jun 25, 2012, at 8:17 AM, David Herman wrote:

Implicit coercions suck, and IMO it would be better if destructuring didn't add new ones to JS. In the current draft spec, I believe if you match an object destructuring pattern against a non-object or an object that doesn't have one or more of the properties, it quietly proceeds -- masking what is quite likely an error:

What it currently does, its

    if value is neither null or undefined, then
          Let obj be ToObject(value)
    Else, let obj be undefined

Does the Else clause lead to obj=undefined being dereferenced resulting in a TypeError?

obj is then the value that is destructured. This happens recursively at each level of the de-structuring.

There are really three separable issues that Dave talks about: 1) The ToObject conversion shown above 2) The handling of null/undefined shown above. 3) The handler of missing properties

  1. ToObject conversion is used pervasively throughout the ES semantics to coerce primitive values to object values. It is an important part of the illusion that for most purposes Number, String, and Boolean primitive values can be treated as-if they were objects.

In particular, I don't see any particular reason why it isn't perfectly reasonable to say things like:

let {concat, indexOf, lastIndexOf} = "";  //get some string methods
let {length,10:char10} = "this is a long string and I don't want to count chars";

let {toLocaleSring: oldToLocale} = 1.0;

There is a tension between fail-fast and the internal consistency of the language. In the case of primitive value conversion I don't think we should deviate from the existing consistent behavior of the language semantics. It just make it much more difficult for a programmer to develop a conceptual model of how the language works, short of memorizing all the special cases. What is the explanation for why: let oldToLocale = 1.0.toLocaleString; would work, yet let {toLocaleSring: oldToLocale} = 1.0; doesn't. The latter feels like somebody made an arbitrary change to the general rules that the language follows.

Yes, and that's why Lars Hansen specified destructuring (and first implemented it for array patterns in Opera) the fail-soft way that JS1.7 implements. This was in ES4 days.

The wiki'ed harmony proposal:

harmony:destructuring

specifies that var {foo} = null; and the like throw on T.foo for T = null.

On your point that by making destructuring not use ToObject, rather throw on any primitive RHS, we would be splitting the language between pre-ES6 "convert implicitly" and ES6 "require explicit ToObject" worlds.

I agree this is a split that we should consider carefully, not just dogmatically assert "EIBTI" and forge ahead. Still, I'm not sure it's worse to keep the implicit ToObject given the new syntax. I used to feel more strongly about it, but less so as time has worn on.

Vesper Lynd (to James Bond): "You've got a choice, you know. Just because you've done something doesn't mean you have to keep doing it." ("Casino Royale", 2006)

(Not that I am comparing JS's implicit conversions to killing people!)

  1. Is certainly debatable as it is a deviation from the ToObject rules that are applied in most other contexts of the language. I believe it is currently specified that way because that is what Firefox did.

No:

js> var {foo, bar} = null

typein:1: TypeError: null has no properties

But I'm unclear on the purpose of the ES6 draft's If/Else you show (cited first above). Why shouldn't we just use ToObject? That is what SpiderMonkey has done lo these many years (2006, IIRC).

I don't have any particular issue eliminating that deviation and applying the standard ToObject rules which means that:" let {a,b} =- null; would throw.

The deviation is solely in ES6 (if anywhere; doesn't obj in the spec get set to undefined by the Else clause, and then undefined.P or undefined[I] evaluated, throwing?). It certainly should be fixed.

  1. Again we have a internal consistency issue:

    let myFoo = {a:0,b:1}.foo; vs let {foo: myFoo} = {a:0,b:1};

why should one work and the other fail?

Lars' design intention, exactly.

Here I just wrote in reply that I could go along with throwing, provided we add ?foo. But that was because I know Mozilla-targeted JS that counts on pulling out undefined. It's not a bug to count on this, real code does it and I suspect there's Opera-specific code doing the same with array patterns.

So my concern was making the fix for such code to work with a throw-on-missing-property destructuring standard, if we create one, be as short and simple to patch as possible.

But perhaps I was wrong to conceded this point too. It's another example of splitting destructuring from structuring (object literals composed with . as you show) and with "pre-ES6" JS. We should consider the split, in this specific form and in general, more carefully -- I mean, with more discussion and ideally some evidence that imputing undefined rather than throwing is better at fighting bugs and not just frustrating "optional" destructuring patterns.

The general rule of JS is that accessing a missing property returns undefined. Whether or not you think it is a good rule, it's the way the core or the language works and it can't change. It's also an easy rule for anybody to learn. Making exceptions to that rule just makes the language less internally consistent, harder to learn, and more complex. I don't think we should do it.

Ok, we should discuss this at the July meeting. Helpful if we get some evidence-based adductive arguments here first.

(b) Allow a shorthand "?" prefix for properties in an object destructuring pattern. This is simply shorthand for defaulting to undefined. IOW, the following are all equivalent:

var { ?foo } = getFoo();
var { foo = undefined } = getFoo();
var { foo: foo = undefined } = getFoo();
var { ?foo: foo } = getFoo();

But var ?foo = getFoo(); would be syntactically illegal!

Yes, the ? prefix would be only inside array and object patterns. That's yet more "inconsistency" by one way of measuring consistency.

If we really wanted to address this problem, I think a better approach that would have internal consistency would be to allow a ? before a binding identifer meaning throw if undefiled.

LOL, "undefined" (which some might say is defiled ;-).

let foo = getFoo(); //assigns undefined if that is what is return from getFoo() let ?foo = getFoo(); //Throws if getFoo() returns undefined. let {foo} = getFoo(); //initialized foo to undefined if obj returned from getFoo() does not have a foo property or its value is undefined let {?foo} = getFoo(); //Throws if obj returned from getFoo() does not have a foo property or its value is undefined

The sense of ? as "maybe" is backwards here, though. The typical trope when insisting on something "succeeding" is ! not ?:

let !foo = getFoo(); // throws if getFoo() returns undefined

But you've talked me out of the ? shorthand, so I am going to back off to not supporting Dave's proposal.

The fundamental question is: should we add new forms that are similar to (or duals of) existing forms but that throw on missing properties, where existing forms impute undefined?

# Claus Reinke (6 years ago)
  1. Again we have a internal consistency issue:

let myFoo = {a:0,b:1}.foo; vs let {foo: myFoo} = {a:0,b:1};

why should one work and the other fail? The general rule of JS is that accessing a missing property returns undefined.

Ultimately, I'd like to have both:

  • irrefutable matching: here, the two phrases above are equivalent, as in the destructuring proposal

  • refutable matching: here, the latter phrase would fail, ideally in a way that can be recovered from efficiently, to guard against interface mismatches and to implement pattern matching with fall through, as in the pattern_matching strawman "switch/match"

One is a syntactic convenience for selection only, the other for selection plus structural tests (actually: behavioral interface tests, see below). Both have their uses.

This suggests having pattern syntax modifiers for indicating refutable or irrefutable matching, and deciding which of the two is likely to be more common (and thus to be the default for patterns without modifiers).

That would also make pattern semantics more uniform, instead of selecting refutable or irrefutable semantics based on context (switch/match or other).

There is a separate question of structural vs behavioral matching. Functional programming fans like me will be tempted to think of the former, but in current JS, only the latter seems to make sense:

  • values can be coerced to objects
  • objects can inherit properties via proto
  • getters are virtual properties
  • proxies can front for objects

So the question is less "how the target is laid out in memory" and more "how the target behaves". I find it useful to remind myself of this difference between JS and other functional languages.

On the other hand, there are strawmen for non-object data structures, such as records. I don't know whether those will

  • tip the balance, so that both structural and behavioral matching will be needed,
  • make the question moot, because we will use records when we mean structural matching and objects when we mean behavioral matching

Since ES cannot easily deprecate functionality, let alone syntax, it would be good to look ahead: how do destructuring and matching relate? what will it mean to destructure records instead of objects? How do functional programming patterns based on structural matching translate to the multi-paradigm language JS, with its behavioral matching?

Claus

# Brendan Eich (6 years ago)

Claus Reinke wrote:

Ultimately, I'd like to have both:

  • irrefutable matching: here, the two phrases above are equivalent, as in the destructuring proposal

  • refutable matching: here, the latter phrase would fail, ideally in a way that can be recovered from efficiently, to guard against interface mismatches and to implement pattern matching with fall through, as in the pattern_matching strawman "switch/match"

One is a syntactic convenience for selection only, the other for selection plus structural tests (actually: behavioral interface tests, see below). Both have their uses.

Agreed.

Also don't get your hopes up for structural as in record types. We can't future-proof against all futures, but we have a dual form to object and array literals in ES6: destructuring. This means making some choices, now, even if in the future we add new forms that make other choices.

This suggests having pattern syntax modifiers for indicating refutable or irrefutable matching, and deciding which of the two is likely to be more common (and thus to be the default for patterns without modifiers).

If there's a modifier-free default, then there's not much case for an explicit modifier. The only future-proofing is to allow a new syntactic form to use a destructuring pattern to do a refutable match. That's what Dave's proposal:

strawman:pattern_matching

was about, but it didn't make ES6 for want of time to fully bake. Still, apart from the catch clause issue, it uses new syntax to imply "refutable, not the default".

So I don't think we need two modifiers, and from the duality of object/array literals and destructuring, it seems to me that destructuring must be irrefutable in the sense you give: the equivalence between

let myFoo = {a:0,b:1}.foo;

and

let {foo: myFoo} = {a:0,b:1};

# Andreas Rossberg (6 years ago)

On 25 June 2012 19:23, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

  1. Again we have a internal consistency issue:

let myFoo = {a:0,b:1}.foo; vs   let {foo: myFoo} = {a:0,b:1};

why should one work and the other fail?  The general rule of JS is that accessing a missing property returns undefined. Whether or not you think it is a good rule, it's the way the core or the language works and it can't change.  It's also an easy rule for anybody to learn.  Making exceptions to that rule just makes the language less internally consistent,  harder to learn, and more complex. I don't think we should do it.

I agree that consistency is an important consideration, but it can cut many ways.

In particular, if there is at least some consensus that we might want to have pattern matching at a later point, then we might want to ensure reasonable consistency between destructuring and pattern matching as well. Frankly, I cannot see how that can be achieved with destructuring that is irrefutable by default (except for some corner cases where it isn't, a minor inconsistency on its own). The potential inconsistency lurking there will likely be much bigger and more confusing than the rather smallish issue we are discussing here.

(And that is leaving aside all usability considerations for a moment.)

# Brendan Eich (6 years ago)

Andreas Rossberg wrote:

On 25 June 2012 19:23, Allen Wirfs-Brock<allen at wirfs-brock.com> wrote:

  1. Again we have a internal consistency issue:

    let myFoo = {a:0,b:1}.foo; vs let {foo: myFoo} = {a:0,b:1};

why should one work and the other fail? The general rule of JS is that accessing a missing property returns undefined. Whether or not you think it is a good rule, it's the way the core or the language works and it can't change. It's also an easy rule for anybody to learn. Making exceptions to that rule just makes the language less internally consistent, harder to learn, and more complex. I don't think we should do it.

I agree that consistency is an important consideration, but it can cut many ways.

In particular, if there is at least some consensus that we might want to have pattern matching at a later point, then we might want to ensure reasonable consistency between destructuring and pattern matching as well.

What consistency do you mean by "reasonable"? If the difference between destructuring and pattern matching is exactly the irrefutable vs. refutable one, then there's no issue with leaving destructuring as proposed and implemented in SpiderMonkey, Rhino, and Opera (draft ES6 spec bugs not included).

Frankly, I cannot see how that can be achieved with destructuring that is irrefutable by default (except for some corner cases where it isn't, a minor inconsistency on its own).

Here you use irrefutable to mean something other than what Claus meant. Can you define it afresh? Thanks.

# Brendan Eich (6 years ago)

Brendan Eich wrote:

and implemented in SpiderMonkey, Rhino, and Opera [...]

Note implementations.

(And that is leaving aside all usability considerations for a moment.)

I meant to add that we have some usability experience, user-testing and bug-reporting by various means, based on the implementations. Imputing undefined for missing properties has not been an issue. The ToObject has not been an issue. You can discount this since it's just anecdotal or whatever. It counts for something with me, especially compared to speculations and assertions that there's a big problem. If there were, wouldn't we know by now?

# Claus Reinke (6 years ago)

Ultimately, I'd like to have both:

  • irrefutable matching:
  • refutable matching:

One is a syntactic convenience for selection only, the other for selection plus structural tests (actually: behavioral interface tests, see below). Both have their uses.

This suggests having pattern syntax modifiers for indicating refutable or irrefutable matching, and deciding which of the two is likely to be more common (and thus to be the default for patterns without modifiers).

Assuming, for this discussion, that '!' marks a refutable pattern, while unmarked patterns remain irrefutable by default, we could then keep

let {foo: myFoo} = {a:0,b:1};

as a shorthand for

let myFoo = {a:0,b:1}.foo;

while writing

let !{foo: myFoo} = {a:0,b:1};
..

as a shorthand for something like

switch ({a:0,b:1}) {
match {foo: myFoo}:
    ..
    break;
default:
    throw "match failure"
}

This kind of interface check with early failure is a weaker, but still useful, form of the compile-time check in module imports. Compare

let !{foo: myFoo} = {a:0,b:1};

and

import {foo: myFoo} from ToModule({a:0,b:1}); // is this permitted?

If the latter is not permitted, the former at least allows for declarative interface specification and early failure, if the export interface of a dynamic module does not match the intended import interface.

If the latter is permitted, why not allow a similar check for non-Module objects, using let '!pattern = object'?

That would also make pattern semantics more uniform, instead of selecting refutable or irrefutable semantics based on context (switch/match or other).

Apart from module imports and pattern matching, there is another question/relation to consider:

  • refutable "structure" matches are a form of type guards, which are another strawman for ES.future; if type guards are not limited to switch/match statements, why should refutable patterns be?

Claus

# Andreas Rossberg (6 years ago)

On 26 June 2012 19:43, Brendan Eich <brendan at mozilla.org> wrote:

Andreas Rossberg wrote:

On 25 June 2012 19:23, Allen Wirfs-Brock<allen at wirfs-brock.**com<allen at wirfs-brock.com>> wrote:

  1. Again we have a internal consistency issue:

    let myFoo = {a:0,b:1}.foo; vs let {foo: myFoo} = {a:0,b:1};

why should one work and the other fail? The general rule of JS is that accessing a missing property returns undefined. Whether or not you think it is a good rule, it's the way the core or the language works and it can't change. It's also an easy rule for anybody to learn. Making exceptions to that rule just makes the language less internally consistent, harder to learn, and more complex. I don't think we should do it.

I agree that consistency is an important consideration, but it can cut many ways.

In particular, if there is at least some consensus that we might want to have pattern matching at a later point, then we might want to ensure reasonable consistency between destructuring and pattern matching as well.

What consistency do you mean by "reasonable"? If the difference between destructuring and pattern matching is exactly the irrefutable vs. refutable one, then there's no issue with leaving destructuring as proposed and implemented in SpiderMonkey, Rhino, and Opera (draft ES6 spec bugs not included).

[Sorry for the late reply, I somehow overlooked this.]

Well, I'm pretty sure that destructuring and pattern matching would both end up having refutable as well as irrefutable cases, at least for some corners. For example, array patterns with rest will probably be refutable no matter what, because they need 'length'. Moreover, we are already discussing operators for turning one into the other, e.g. ? or default bindings.

If you have all that, using the same syntax for patterns in both constructs, but then different default behaviour for some of the patterns (and an opposite set of operators for inverting the default?), I think that would be very confusing.

More abstractly, destructuring is just one-case pattern matching. I'm not aware of any language that has both and defines it otherwise. It would just create unnecessary complexity.

Frankly, I cannot see how that can be achieved with

destructuring that is irrefutable by default (except for some corner cases where it isn't, a minor inconsistency on its own).

Here you use irrefutable to mean something other than what Claus meant. Can you define it afresh? Thanks.

Hm, I don't think I used it differently. Or did I misunderstand what Claus meant?

# Brendan Eich (6 years ago)

Andreas Rossberg wrote:

Hm, I don't think I used it differently. Or did I misunderstand what Claus meant?

/Andreas

From Claus's 26-June message:

  1. Again we have a internal consistency issue:

let myFoo = {a:0,b:1}.foo; vs let {foo: myFoo} = {a:0,b:1};

why should one work and the other fail? The general rule of JS is that accessing a missing property returns undefined.

Ultimately, I'd like to have both:

  • irrefutable matching: here, the two phrases above are equivalent, as in the destructuring proposal

--- end citation ---

Is the equivalence between those two let declarations the same as one-case pattern matching? One-case matching in many languages with "pattern matching" will fail on mismatch, whereas destructuring that preserves the above equivalence must succeed and initialize myFoo to undefined.

Should we call the fail (throw) vs. succeed distinction something other than refutable vs. irrefutable?

# Andreas Rossberg (6 years ago)

On 2 July 2012 20:17, Brendan Eich <brendan at mozilla.org> wrote:

Andreas Rossberg wrote:

Hm, I don't think I used it differently. Or did I misunderstand what Claus meant?

/Andreas

From Claus's 26-June message:

  1. Again we have a internal consistency issue:

let myFoo = {a:0,b:1}.foo; vs let {foo: myFoo} = {a:0,b:1};

why should one work and the other fail? The general rule of JS is that accessing a missing property returns undefined.

Ultimately, I'd like to have both:

  • irrefutable matching: here, the two phrases above are equivalent, as in the destructuring proposal

--- end citation ---

Is the equivalence between those two let declarations the same as one-case pattern matching? One-case matching in many languages with "pattern matching" will fail on mismatch, whereas destructuring that preserves the above equivalence must succeed and initialize myFoo to undefined.

Should we call the fail (throw) vs. succeed distinction something other than refutable vs. irrefutable?

Hm, I suppose I'm a little confused now what terminology we are actually trying to resolve. AFAICT, the meaning of refutable vs irrefutable is independent of any equivalences you may or may not want to hold. (Refutable: a pattern that may fail to match, raising an error; irrefutable: a pattern that is matched by anything, but potentially by binding nullish values to pattern variables.)

The current draft tries to make all/most destructuring patterns irrefutable. The above equivalence is the motivation (though not the "definition" of irrefutable).

I understand that motivation, but was trying to point out that it is merely one under several (conflicting) potential equivalences to consider. For example, in languages with pattern matching I usually have

let pat = exp; stats ~= match (exp) { case pat: stats }

(Assuming a match without a default throws, otherwise add an explicit error default.)

IMHO this equivalence would be much more important, because it is necessary for giving a uniform meaning to patterns per se.

# Brendan Eich (6 years ago)

Andreas Rossberg wrote:

(Refutable: a pattern that may fail to match, raising an error; irrefutable: a pattern that is matched by anything, but potentially by binding nullish values to pattern variables.)

(That is indeed the definition we all share -- thanks.)

The current draft tries to make all/most destructuring patterns irrefutable. The above equivalence is the motivation (though not the "definition" of irrefutable).

Sure (I didn't write "definition"). That motivation comes from JS as it is today, without pattern matching.

I understand that motivation, but was trying to point out that it is merely one under several (conflicting) potential equivalences to consider. For example, in languages with pattern matching I usually have

let pat = exp; stats ~= match (exp) { case pat: stats }

(Assuming a match without a default throws, otherwise add an explicit error default.)

IMHO this equivalence would be much more important, because it is necessary for giving a uniform meaning to patterns per se.

If only JS had pattern matching!

We may add pattern matching, but does that future possibility make the equivalence you cite more important than the one JS programmers might expect today?

We could try to have our cake and eat it, by extending the pattern language with prefix-! or prefix-?. Which prefix depends on the choice of default behavior, based on ranking of the two equivalences. Whatever that ranking, does this make sense so far?

# Andreas Rossberg (6 years ago)

On 4 July 2012 17:01, Brendan Eich <brendan at mozilla.org> wrote:

I understand that motivation, but was trying to point out that it is

merely one under several (conflicting) potential equivalences to consider. For example, in languages with pattern matching I usually have

let pat = exp; stats ~= match (exp) { case pat: stats }

(Assuming a match without a default throws, otherwise add an explicit error default.)

IMHO this equivalence would be much more important, because it is necessary for giving a uniform meaning to patterns per se.

If only JS had pattern matching!

We may add pattern matching, but does that future possibility make the equivalence you cite more important than the one JS programmers might expect today?

Well, note my careful use of the conjunctive. :) That basically was the question I initially asked. My viewpoint is: If there is some reasonable chance that we might add pattern matching at some point in the future, then yes, I think we should not be future hostile, and bear that equivalence in mind as important.

We could try to have our cake and eat it, by extending the pattern language with prefix-! or prefix-?. Which prefix depends on the choice of default behavior, based on ranking of the two equivalences. Whatever that ranking, does this make sense so far?

Sure, I'm all for that. As long as they -- and the rest of the pattern language -- have the same meaning in all contexts.

# Brendan Eich (6 years ago)

Andreas Rossberg wrote:

Well, note my careful use of the conjunctive. :) That basically was the question I initially asked. My viewpoint is: If there is some reasonable chance that we might add pattern matching at some point in the future, then yes, I think we should not be future hostile, and bear that equivalence in mind as important.

We could try to have our cake and eat it, by extending the pattern
language with prefix-! or prefix-?. Which prefix depends on the
choice of default behavior, based on ranking of the two
equivalences. Whatever that ranking, does this make sense so far?

Sure, I'm all for that. As long as they -- and the rest of the pattern language -- have the same meaning in all contexts.

Ok, we are getting somewhere -- thanks for bearing with me (and I hope this thread was helpful to others following, as it was to me).

Then the question is: should we value the current equivalence more and make the pattern-matching equivalence use-case pay the prefix-! syntax. IOW, keep

A:

let {foo} = bar(); =~ let foo = bar().foo;

and impute undefined if foo is missing in the object returned by bar(), and require

{ let {!foo} = exp; console.log(foo) } =~ match (exp) { case !foo; console.log(foo); }

Without the !, the match would bind undefined to the case foo local.

Or should we do as you propose, and require prefix-? to get the impute-undefined-on-missing-property behavior needed for the first equivalence, call it

B:

let {?foo} = bar(); =~ let foo = bar().foo;

At least this way, match does not need ! to work as pattern-matching users expect:

{ let {foo} = exp; console.log(foo) } =~ match (exp) { case foo; console.log(foo); }

Allen rallied me to the prefix-! route by objecting that prefix-? would not be available outside of patterns, which simply highlights how JS already imputes undefined for missing property.

I feel the burden of the first equivalence acutely, since I implemented destructuring per the ES4-era proposal to look like (A) above, based on Opera's engine of the time, and Rhino and probably other engines followed suit. This experience means compatibility breaking pain, and what's more: user-testing something new, unscientifically as ever but in much less time than we've had to hear about bugs due to (A).

However, I agree that requiring prefix-! in

match (exp) { case !foo: ... }

is onerous if we choose the impute-undefined path, and worse: that a missing prefix-! is both likely to happen (a footgun), and nonsensical for pattern matching.

So (B) wins if we want pattern-matching.

We should talk more at this month's TC39 meeting, but I see a sharp divide ahead:

  1. Choose (A), possibly with modification, either rejecting useful pattern matching decisively, or else making its pattern language differ by making prefix-! implicit. But you rejected splitting pattern meaning across destructuring and future pattern-matching, in the last sentence cited above.

XOR

  1. Choose (B) because future-proofing for pattern matching wants prefix-?, and impose a compatibility break from what we and others implemented, and what most JS users might expect based on JS's carefree imputing of undefined.

Comments from es-discuss peanut gallery welcome.

I could go for 1/A, except for two facts. First, I believe there is a non-trivial body of Firefox JS that depends on imputing undefined. Second, and what is more important: we haven't usability-tested 2/B at all.

# Claus Reinke (6 years ago)
We could try to have our cake and eat it, by extending the pattern
language with prefix-! or prefix-?. Which prefix depends on the
choice of default behavior, based on ranking of the two
equivalences. Whatever that ranking, does this make sense so far?

Sure, I'm all for that. As long as they -- and the rest of the pattern language -- have the same meaning in all contexts.

Comments from es-discuss peanut gallery welcome.

Some peanuts:

This consistency of meaning, with the ability to choose refutable or irrefutable patterns wherever patterns are permitted, is important, because fewer special cases lead to an easier-to-understand language.

Current variables in assignments and formal arguments are special cases of more general patterns, and they do not have prefixes. Fortunately, variables behave the same in refutable and irrefutable matching.

When variable patterns get extended with as-patterns, those will make sense in both refutable and irrefutable forms, for different uses.

When patterns get extended with type guards in future, those seem to require refutable matching. Irrefutable type guards might allow for running typed code in an unsafe manner, but one would have to think about what that means (type exceptions on first access?).

From experience with Haskell's patterns, while it makes sense to

have irrefutable patterns nested inside refutable ones, the other direction is less helpful. Haskell's patterns also impact on strictness (whether and how far the argument is evaluated), which is not an issue in JS.

However, if the general issue (that nesting prefers one default over the other) carries over to JS, then making irrefutable patterns the default will lead to lots of prefixes in patterns (marking the whole path through the pattern).

Allen rallied me to the prefix-! route by objecting that prefix-? would not be available outside of patterns, which simply highlights how JS already imputes undefined for missing property.

Could you expand on this? Refutable vs irrefutable only applies to patterns so far, though one could imagine property selectors with early failure to complement the current succeed-with-undefined.

Claus

# Brendan Eich (6 years ago)

Claus Reinke wrote:

Allen rallied me to the prefix-! route by objecting that prefix-? would not be available outside of patterns, which simply highlights how JS already imputes undefined for missing property.

Could you expand on this? Refutable vs irrefutable only applies to patterns so far, though one could imagine property selectors with early failure to complement the current succeed-with-undefined.

Allen's post is up-thread about 14 messages:

esdiscuss/2012-June/023719

# Andreas Rossberg (6 years ago)

On 5 July 2012 20:14, Brendan Eich <brendan at mozilla.org> wrote:

Then the question is: should we value the current equivalence more and make the pattern-matching equivalence use-case pay the prefix-! syntax. IOW, keep

A:

let {foo} = bar(); =~ let foo = bar().foo;

and impute undefined if foo is missing in the object returned by bar(), and require

{ let {!foo} = exp; console.log(foo) } =~ match (exp) { case !foo; console.log(foo); }

Without the !, the match would bind undefined to the case foo local.

Or should we do as you propose, and require prefix-? to get the impute-undefined-on-missing-**property behavior needed for the first equivalence, call it

B:

let {?foo} = bar(); =~ let foo = bar().foo;

At least this way, match does not need ! to work as pattern-matching users expect:

{ let {foo} = exp; console.log(foo) } =~ match (exp) { case foo; console.log(foo); }

Allen rallied me to the prefix-! route by objecting that prefix-? would not be available outside of patterns, which simply highlights how JS already imputes undefined for missing property.

I feel the burden of the first equivalence acutely, since I implemented destructuring per the ES4-era proposal to look like (A) above, based on Opera's engine of the time, and Rhino and probably other engines followed suit. This experience means compatibility breaking pain, and what's more: user-testing something new, unscientifically as ever but in much less time than we've had to hear about bugs due to (A).

However, I agree that requiring prefix-! in

match (exp) { case !foo: ... }

is onerous if we choose the impute-undefined path, and worse: that a missing prefix-! is both likely to happen (a footgun), and nonsensical for pattern matching.

So (B) wins if we want pattern-matching.

We should talk more at this month's TC39 meeting, but I see a sharp divide ahead:

  1. Choose (A), possibly with modification, either rejecting useful pattern matching decisively, or else making its pattern language differ by making prefix-! implicit. But you rejected splitting pattern meaning across destructuring and future pattern-matching, in the last sentence cited above.

XOR

  1. Choose (B) because future-proofing for pattern matching wants prefix-?, and impose a compatibility break from what we and others implemented, and what most JS users might expect based on JS's carefree imputing of undefined.

Agreed.

Comments from es-discuss peanut gallery welcome.

I could go for 1/A, except for two facts. First, I believe there is a non-trivial body of Firefox JS that depends on imputing undefined. Second, and what is more important: we haven't usability-tested 2/B at all.

Did you mean "could go for 2/B" there?

# Brendan Eich (6 years ago)

Andreas Rossberg wrote:

We should talk more at this month's TC39 meeting, but I see a
sharp divide ahead:

1. Choose (A), possibly with modification, either rejecting useful
pattern matching decisively, or else making its pattern language
differ by making prefix-! implicit. But you rejected splitting
pattern meaning across destructuring and future pattern-matching,
in the last sentence cited above.

XOR

2. Choose (B) because future-proofing for pattern matching wants
prefix-?, and impose a compatibility break from what we and others
implemented, and what most JS users might expect based on JS's
carefree imputing of undefined.

Agreed.

Comments from es-discuss peanut gallery welcome. 


I could go for 1/A, except for two facts. First, I believe there
is a non-trivial body of Firefox JS that depends on imputing
undefined. Second, and what is more important: we haven't
usability-tested 2/B at all.

Did you mean "could go for 2/B" there?

Evidently!

I rearranged and renumbered but missed this one. Clearly I meant 2/B. I'm a bit unhappy about making such a change so long after implementing 1/A (without prefix-!). But never mind me -- what do others think?

# Russell Leggett (6 years ago)

On Fri, Jul 6, 2012 at 1:37 PM, Brendan Eich <brendan at mozilla.org> wrote:

Andreas Rossberg wrote:

We should talk more at this month's TC39 meeting, but I see a
sharp divide ahead:

1. Choose (A), possibly with modification, either rejecting useful
pattern matching decisively, or else making its pattern language
differ by making prefix-! implicit. But you rejected splitting
pattern meaning across destructuring and future pattern-matching,
in the last sentence cited above.

XOR

2. Choose (B) because future-proofing for pattern matching wants
prefix-?, and impose a compatibility break from what we and others
implemented, and what most JS users might expect based on JS's
carefree imputing of undefined.

Agreed.

Comments from es-discuss peanut gallery welcome.

I could go for 1/A, except for two facts. First, I believe there
is a non-trivial body of Firefox JS that depends on imputing
undefined. Second, and what is more important: we haven't
usability-tested 2/B at all.

Did you mean "could go for 2/B" there?

Evidently!

I rearranged and renumbered but missed this one. Clearly I meant 2/B. I'm a bit unhappy about making such a change so long after implementing 1/A (without prefix-!). But never mind me -- what do others think?

I would love to see 2/B. A lot of that is likely because I would love to see pattern matching, though, I'll admit. I think that the ? on refutable portions of the pattern would work well with the new default and existential operators, while I think ! will not have a good analog. If I were to throw out another reason based on destructuring assignment alone, and not pattern matching later, I would say that if parts of the destructuring are likely to fail, it would be better to know upfront. If there are optional fields, ? clearly expresses that. If there are wildly unknown structures, destructured assignment is probably not the best approach, or at least there should be some structural testing upfront (pattern matching would of course be ideal).

As for usability testing, while it obviously is not done irrefutably in spidermonkey now, I think we can at least look to other languages. Looking over several languages, all of the ones I have seen are irrefutable. Some are strongly typed languages, but not all. Python does not have complete patterns, it can basically just destructure list, but even it requires that the number of elements be correct. Erlang, OCaml, and F# all allow full pattern assignments, and they will all throw errors if not correctly matched. Obviously, it is a little different than what it would be in ES, they have full on patterns not just destructuring, but it would clearly be all the more consistent when patterns hopefully get added. I know that JS users have come to expect a certain amount of carefree imputing, but destructuring is new (outside of FF), and I think it is certainly an area which has options. I haven't exactly done a formal survey of languages, but from what I've seen JS would be the first to be so lenient. Allowing ? in portions of a pattern, I actually think would be the perfect compromise. People who write very loose code will either have to put ?s on their patterns or keep working the same way they have been, but if there isn't a reasonable amount of checking by default, I think we're going to be seeing some more WATs in presentations. JS gets picked on for how much coercion and silent errors it will chug along with. Best practices of always using === and avoiding 'with' are just a couple of examples.

Finally, I would like to take the approach of looking at the best way to handle the different use cases.

  1. Really simple destructuring with expected results: let {x,y} = getPoint(); In my opinion, this should throw if x or y are not available. It is unlikely that the coder is checking properties here, they are just going to call other functions passing through x or y. It would probably be better to get the error as soon as possible. Much easier to debug that way. In many cases, I think these types of destructuring assignments will lead to effortless checking that would previously have been missed because of the extra effort to add those checks in as if statements.
  2. Options type objects: let {url,callback,?errback} = options; This lets you very clearly decompose an object and declaratively show which values are optional and which ones are not. If all of them are optional, the ? would just go to the left of the brace, but really this should be the minority case for most situations.
  3. A disparate list of possible types of matches. This is common in "overloaded" functions where the number and type of arguments can be wildly different. This is probably the strongest case against irrefutable matching, because it is so likely to result in mismatches which should have logic performed on them instead of having an error thrown. However, it is also the strongest case for pattern matching. There is nothing wrong with wanting to accept differently shaped things, but right now there is not really a great facility for acting on them. Pattern matching would be ideal. I think it would be such a powerful addition to the language as it is used right now, that it honestly pains me that we would cut out the possibility. I would love to see patterns now, but I can understand waiting. Making a decision which would close it off though would be extremely disappointing.

I know that full blown patterns are out of scope (though I'm hoping maybe this discussion might change that), but even if we can't have it now, I think irrefutable destructuring would really make people see the potential. Even if no facility was provided for it, a hypothetical match function could be created which would take function and execute them in sequence in a try catch until one did not fail. I think we can do better than that and should probably try if we go with irrefutable destructuring, but just knowing it would be possible for the community to take and run with is nice.

# Andreas Rossberg (6 years ago)

I agree with almost everything you said. But just to clarify, I think you actually meant "refutable" everywhere you said "irrefutable".

# Russell Leggett (6 years ago)

::faceplam::

# Russell Leggett (6 years ago)

::double-facepalm::

# Russell Leggett (6 years ago)

On Mon, Jul 9, 2012 at 12:36 PM, Andreas Rossberg <rossberg at google.com>wrote:

I agree with almost everything you said. But just to clarify, I think you actually meant "refutable" everywhere you said "irrefutable".

/Andreas

So I corrected my bonehead mistake, here it is again in case anybody

wanted to more cleanly respond to specific points.

======================================

I would love to see 2/B. A lot of that is likely because I would love to see pattern matching, though, I'll admit. I think that the ? on refutable portions of the pattern would work well with the new default and existential operators, while I think ! will not have a good analog. If I were to throw out another reason based on destructuring assignment alone, and not pattern matching later, I would say that if parts of the destructuring are likely to fail, it would be better to know upfront. If there are optional fields, ? clearly expresses that. If there are wildly unknown structures, destructured assignment is probably not the best approach, or at least there should be some structural testing upfront (pattern matching would of course be ideal).

As for usability testing, while it obviously is not done refutably in spidermonkey now, I think we can at least look to other languages. Looking over several languages, all of the ones I have seen are refutable. Some are strongly typed languages, but not all. Python does not have complete patterns, it can basically just destructure list, but even it requires that the number of elements be correct. Erlang, OCaml, and F# all allow full pattern assignments, and they will all throw errors if not correctly matched. Obviously, it is a little different than what it would be in ES, they have full on patterns not just destructuring, but it would clearly be all the more consistent when patterns hopefully get added. I know that JS users have come to expect a certain amount of carefree imputing, but destructuring is new (outside of FF), and I think it is certainly an area which has options. I haven't exactly done a formal survey of languages, but from what I've seen JS would be the first to be so lenient. Allowing ? in portions of a pattern, I actually think would be the perfect compromise. People who write very loose code will either have to put ?s on their patterns or keep working the same way they have been, but if there isn't a reasonable amount of checking by default, I think we're going to be seeing some more WATs in presentations. JS gets picked on for how much coercion and silent errors it will chug along with. Best practices of always using === and avoiding 'with' are just a couple of examples.

Finally, I would like to take the approach of looking at the best way to handle the different use cases. Really simple destructuring with expected results: let {x,y} = getPoint(); In my opinion, this should throw if x or y are not available. It is unlikely that the coder is checking properties here, they are just going to call other functions passing through x or y. It would probably be better to get the error as soon as possible. Much easier to debug that way. In many cases, I think these types of destructuring assignments will lead to effortless checking that would previously have been missed because of the extra effort to add those checks in as if statements. Options type objects: let {url,callback,?errback} = options; This lets you very clearly decompose an object and declaratively show which values are optional and which ones are not. If all of them are optional, the ? would just go to the left of the brace, but really this should be the minority case for most situations. A disparate list of possible types of matches. This is common in "overloaded" functions where the number and type of arguments can be wildly different. This is probably the strongest case against refutable matching, because it is so likely to result in mismatches which should have logic performed on them instead of having an error thrown. However, it is also the strongest case for pattern matching. There is nothing wrong with wanting to accept differently shaped things, but right now there is not really a great facility for acting on them. Pattern matching would be ideal. I think it would be such a powerful addition to the language as it is used right now, that it honestly pains me that we would cut out the possibility. I would love to see patterns now, but I can understand waiting. Making a decision which would close it off though would be extremely disappointing. I know that full blown patterns are out of scope (though I'm hoping maybe this discussion might change that), but even if we can't have it now, I think refutable destructuring would really make people see the potential. Even if no facility was provided for it, a hypothetical match function could be created which would take function and execute them in sequence in a try catch until one did not fail. I think we can do better than that and should probably try if we go with refutable destructuring, but just knowing it would be possible for the community to take it and run with it is nice.

# Brendan Eich (6 years ago)

Russell Leggett wrote:

Options type objects: let {url,callback,?errback} = options; This lets you very clearly decompose an object and declaratively show which values are optional and which ones are not. If all of them are optional, the ? would just go to the left of the brace, but really this should be the minority case for most situations.

Dave and I had talked about prefix-? as applying only to property names, since that covers all but the top level for object patterns:

{required, ?optional_shallow} {required, ?optional_deep: {foo, bar, ?baz}}

but of course it leaves out the top level.

If we make prefix-? apply to the "value" side of the ":" (which in object patterns is optional, but if present names the binding not the property), then we have

{required, optional_shallow: ?renamed_optional_shallow} {required, optional_deep: ?{foo, bar, ?baz}}

and the object shorthand would still be wanted:

{required, ?optional_shallow}

This is a strictly larger grammar, though. If we can keep prefix-? in the property name side, then all we lose is the top level.

Array patterns don't have explicit "0", "1", "2", etc., property names, though so either prefix-? doesn't work, or you have to use an object pattern (which works on array values), or else prefix-? goes before the binding name (the "value" side) as above.

A disparate list of possible types of matches. This is common in "overloaded" functions where the number and type of arguments can be wildly different. This is probably the strongest case against refutable matching, because it is so likely to result in mismatches which should have logic performed on them instead of having an error thrown.

Right, this minority case could be left out. The array pattern situation is stronger motivation for prefix-? applying to bindings not property names.

# Russell Leggett (6 years ago)

On Mon, Jul 9, 2012 at 4:04 PM, Brendan Eich <brendan at mozilla.org> wrote:

Russell Leggett wrote:

Options type objects: let {url,callback,?errback} = options; This lets you very clearly decompose an object and declaratively show which values are optional and which ones are not. If all of them are optional, the ? would just go to the left of the brace, but really this should be the minority case for most situations.

Dave and I had talked about prefix-? as applying only to property names, since that covers all but the top level for object patterns:

{required, ?optional_shallow} {required, ?optional_deep: {foo, bar, ?baz}}

but of course it leaves out the top level.

This makes a lot of sense. It is certainly smaller. I do worry, though, that it might fall short for the people that would like irrefutable destructuring. I had imagined that ?{a,b,c} could be used in a case where all properties would be optional. I did have a question about refutability. What would happen here?

let {?a,?b} = null;

Even though both properties are optional, would this still fail? Does the {} imply some structure? What if we were thinking about actual pattern matching in the future? Would this just match anything? If we say it should not match anything, then you would need ?{} to make it fully irrefutable.

If we make prefix-? apply to the "value" side of the ":" (which in object patterns is optional, but if present names the binding not the property), then we have

{required, optional_shallow: ?renamed_optional_shallow} {required, optional_deep: ?{foo, bar, ?baz}}

and the object shorthand would still be wanted:

{required, ?optional_shallow}

This is a strictly larger grammar, though. If we can keep prefix-? in the property name side, then all we lose is the top level.

Array patterns don't have explicit "0", "1", "2", etc., property names, though so either prefix-? doesn't work, or you have to use an object pattern (which works on array values), or else prefix-? goes before the binding name (the "value" side) as above.

A disparate list of possible types of matches. This is common in

"overloaded" functions where the number and type of arguments can be wildly different. This is probably the strongest case against refutable matching, because it is so likely to result in mismatches which should have logic performed on them instead of having an error thrown.

Right, this minority case could be left out. The array pattern situation is stronger motivation for prefix-? applying to bindings not property names.

Yes, I think that arrays are likely the strongest case for ? on bindings. Something like this would be nice: let [first,last,?company] = contact.split(",");

And certainly, if you wanted to be able to destructure a list of arguments, some of which being optional, it would be very useful.

Ultimately, while I think I could personally do fine with fewer points of prefix-?, I can see a few cases for it, especially on arrays. I worry that without supporting these, refutable by default won't be able to reach consensus.

# Russell Leggett (6 years ago)

Another thing that I was also thinking is that it might look a little nicer if the ? was a post-fix instead of a pre-fix.

let {first, last, company?} = contact;

I might be missing why this wouldn't work out, but it aesthetically just looks right to me. It looks like the regex operator, and is also obviously the position it would be in english.

# Brendan Eich (6 years ago)

Russell Leggett wrote:

On Mon, Jul 9, 2012 at 4:04 PM, Brendan Eich <brendan at mozilla.org <mailto:brendan at mozilla.org>> wrote:

Russell Leggett wrote:

    Options type objects:
        let {url,callback,?errback} = options;
    This lets you very clearly decompose an object and
    declaratively show which values are optional and which ones
    are not. If all of them are optional, the ? would just go to
    the left of the brace, but really this should be the minority
    case for most situations.


Dave and I had talked about prefix-? as applying only to property
names, since that covers all but the top level for object patterns:

  {required, ?optional_shallow}
  {required, ?optional_deep: {foo, bar, ?baz}}

but of course it leaves out the top level.

This makes a lot of sense. It is certainly smaller. I do worry, though, that it might fall short for the people that would like irrefutable destructuring. I had imagined that ?{a,b,c} could be used in a case where all properties would be optional.

Yup. But as your previous mail noted, that might be a rare (hard) case and hard cases make bad law. Or so they teach law students!

I did have a question about refutability. What would happen here?

let {?a,?b} = null;

Even though both properties are optional, would this still fail? Does the {} imply some structure?

This is the separate ToObject vs. must-be-of-spec-Object-type issue. JS in SpiderMonkey, Rhino, probably other engines today:

js> let {a,b} = null

typein:1: TypeError: null has no properties js> let {a,b} = undefined

typein:2: TypeError: undefined has no properties js> let {a,b} = 42

js> a

js> b

We need to resolve this one independently from the presence of prefix-? on the left.

Right, this minority case could be left out. The array pattern
situation is stronger motivation for prefix-? applying to bindings
not property names.

Yes, I think that arrays are likely the strongest case for ? on bindings. Something like this would be nice: let [first,last,?company] = contact.split(",");

And certainly, if you wanted to be able to destructure a list of arguments, some of which being optional, it would be very useful.

Ultimately, while I think I could personally do fine with fewer points of prefix-?, I can see a few cases for it, especially on arrays. I worry that without supporting these, refutable by default won't be able to reach consensus.

I bet you are right. We'll be taking this up in a couple of weeks, it's on the agenda. Thanks for your posts on the whole issue.

# Brendan Eich (6 years ago)

Russell Leggett wrote:

Another thing that I was also thinking is that it might look a little nicer if the ? was a post-fix instead of a pre-fix.

let {first, last, company?} = contact;

I might be missing why this wouldn't work out, but it aesthetically just looks right to me. It looks like the regex operator, and is also obviously the position it would be in english.

We have to parse LHS-of-assignment patterns using the Expression cover grammar, so this does not work in general due to ?:.

If we parse only in binding contexts (let, const, var on the left, or formal params and catch clauses), then we could use a different pattern grammar. Worth breaking uniformity with assignment expressions?

# Russell Leggett (6 years ago)

On Tue, Jul 10, 2012 at 10:40 AM, Brendan Eich <brendan at mozilla.org> wrote:

Russell Leggett wrote:

Another thing that I was also thinking is that it might look a little nicer if the ? was a post-fix instead of a pre-fix.

let {first, last, company?} = contact;

I might be missing why this wouldn't work out, but it aesthetically just looks right to me. It looks like the regex operator, and is also obviously the position it would be in english.

We have to parse LHS-of-assignment patterns using the Expression cover grammar, so this does not work in general due to ?:.

If we parse only in binding contexts (let, const, var on the left, or formal params and catch clauses), then we could use a different pattern grammar. Worth breaking uniformity with assignment expressions?

Right, I've mostly been thinking about only declaration forms of assignment, however, thinking about it now, wouldn't it be only a single token of lookahead to disambiguate? A postfix "optional" operator could only be followed by , ] } or = (when in the value position) - none of which would be valid tokens for the ternary operator. If it is on an object property, it could actually be followed by a :, but that is unambiguous because it is a property, not an expression.

# Brendan Eich (6 years ago)

Russell Leggett wrote:

On Tue, Jul 10, 2012 at 10:40 AM, Brendan Eich <brendan at mozilla.org <mailto:brendan at mozilla.org>> wrote:

Russell Leggett wrote:

    Another thing that I was also thinking is that it might look a
    little nicer if the ? was a post-fix instead of a pre-fix.

        let {first, last, company?} = contact;

    I might be missing why this wouldn't work out, but it
    aesthetically just looks right to me. It looks like the regex
    operator, and is also obviously the position it would be in
    english.


We have to parse LHS-of-assignment patterns using the Expression
cover grammar, so this does not work in general due to ?:.

If we parse only in binding contexts (let, const, var on the left,
or formal params and catch clauses), then we could use a different
pattern grammar. Worth breaking uniformity with assignment
expressions?

Right, I've mostly been thinking about only declaration forms of assignment, however, thinking about it now, wouldn't it be only a single token of lookahead to disambiguate? A postfix "optional" operator could only be followed by , ] } or = (when in the value position) - none of which would be valid tokens for the ternary operator. If it is on an object property, it could actually be followed by a :, but that is unambiguous because it is a property, not an expression.

You're right, we could use lookahead restrictions. Maybe it's worth it -- Dave (when back from vacation), Andreas and Allen should pipe up.

# Andreas Rossberg (6 years ago)

On 10 July 2012 10:17, Russell Leggett <russell.leggett at gmail.com> wrote:

Yes, I think that arrays are likely the strongest case for ? on bindings. Something like this would be nice: let [first,last,?company] = contact.split(",");

Actually, I don't see how this is a case of "? on bindings". In fact, the '?' would have to be interpreted as part of the array pattern syntax for the above to make sense (in analogy to object patterns, it happens to be associated with the implicit property name "2").

AFAICS, '?' on a variable itself would always be redundant, because a variable pattern is irrefutable anyway.

# Andreas Rossberg (6 years ago)

On 10 July 2012 10:47, Russell Leggett <russell.leggett at gmail.com> wrote:

Another thing that I was also thinking is that it might look a little nicer if the ? was a post-fix instead of a pre-fix.

let {first, last, company?} = contact;

I might be missing why this wouldn't work out, but it aesthetically just looks right to me. It looks like the regex operator, and is also obviously the position it would be in english.

Even parsing issues aside, I don't like postfix better. It is easier to miss, especially if ? was possible on patterns, not just properties. Consider:

let {the, that: {x, y}, those, theirs: {what, why}, thus}? = callSomething()

How easy is it to miss the final '?' when looking for the binding of 'x'?

But even on longish property names it can be a tad too easy to overlook:

let {what, why, whatTheHeckIsGoingOn?} = callSomething()

# Russell Leggett (6 years ago)

On Wed, Jul 11, 2012 at 7:14 AM, Andreas Rossberg <rossberg at google.com>wrote:

On 10 July 2012 10:17, Russell Leggett <russell.leggett at gmail.com> wrote:

Yes, I think that arrays are likely the strongest case for ? on bindings. Something like this would be nice: let [first,last,?company] = contact.split(",");

Actually, I don't see how this is a case of "? on bindings". In fact, the '?' would have to be interpreted as part of the array pattern syntax for the above to make sense (in analogy to object patterns, it happens to be associated with the implicit property name "2").

Well, I mean, if you could put it on {}, [], and on variable names, it would have to be part of the expression cover grammer as Brendan was talking about. I suppose technically I should have said "? on expressions that can be interpreted as patterns" or something.

In the array case, I guess it would be the equivalent of

let {0:first, 1:last, ?2:company} = contact.split(",");

Although that highlights an interesting point about object patterns, perhaps it was already covered. If the property is not a valid identifier, it could not use the property shorthand form. This should be an early error.

Also related, is there any structural requirement for the value being matched in the case of a [] pattern? If you use ..., there is a requirement to support length.

AFAICS, '?' on a variable itself would always be redundant, because a variable pattern is irrefutable anyway.

So you're saying that even this should match in a refutable pattern:

let [a,b,c] = [1,2];

I would expect that to fail, especially in a hypothetical pattern matching construct.

# Russell Leggett (6 years ago)

On Wed, Jul 11, 2012 at 7:16 AM, Andreas Rossberg <rossberg at google.com>wrote:

On 10 July 2012 10:47, Russell Leggett <russell.leggett at gmail.com> wrote:

Another thing that I was also thinking is that it might look a little nicer if the ? was a post-fix instead of a pre-fix.

let {first, last, company?} = contact;

I might be missing why this wouldn't work out, but it aesthetically just looks right to me. It looks like the regex operator, and is also obviously the position it would be in english.

Even parsing issues aside, I don't like postfix better. It is easier to miss, especially if ? was possible on patterns, not just properties. Consider:

let {the, that: {x, y}, those, theirs: {what, why}, thus}? = callSomething()

How easy is it to miss the final '?' when looking for the binding of 'x'?

But even on longish property names it can be a tad too easy to overlook:

let {what, why, whatTheHeckIsGoingOn?} = callSomething()

I know what you're saying, but I think it is widely used as postfix. People are used to it. And it reads fine to me here:

Pattern ::= "{" (Field ("," Field)* ","?)? "}" | "[" ArrayPatternList "]" ArrayPatternList ::= "..." Element | Element? ("," Element?)* ("," "..." Element)? Element ::= Pattern | LValue Field ::= Identifier (":" Element)? LValue ::= <any lvalue expression allowed in a normal assignment expression>

# Andreas Rossberg (6 years ago)

On 11 July 2012 17:20, Russell Leggett <russell.leggett at gmail.com> wrote:

On Wed, Jul 11, 2012 at 7:14 AM, Andreas Rossberg <rossberg at google.com>wrote:

AFAICS, '?' on a variable itself would always be redundant, because a variable pattern is irrefutable anyway.

So you're saying that even this should match in a refutable pattern:

let [a,b,c] = [1,2];

I would expect that to fail, especially in a hypothetical pattern matching construct.

That would fail because the array pattern is refutable. What I meant is something else, namely that there is no difference between these:

let x = ... let ?x = ...

or these:

let {x: x, y: y} = ... let {x: ?x, y: ?y} = ...

Pattern matching recursively decomposes the RHS and matches a (sub)value against the respective (sub)pattern of the LHS. Once you reach a variable, that submatch is unconditional, so a '?' doesn't change anything.

# Russell Leggett (6 years ago)

On Wed, Jul 11, 2012 at 11:31 AM, Andreas Rossberg <rossberg at google.com>wrote:

On 11 July 2012 17:20, Russell Leggett <russell.leggett at gmail.com> wrote:

On Wed, Jul 11, 2012 at 7:14 AM, Andreas Rossberg <rossberg at google.com>wrote:

AFAICS, '?' on a variable itself would always be redundant, because a variable pattern is irrefutable anyway.

So you're saying that even this should match in a refutable pattern:

let [a,b,c] = [1,2];

I would expect that to fail, especially in a hypothetical pattern matching construct.

That would fail because the array pattern is refutable. What I meant is something else, namely that there is no difference between these:

let x = ... let ?x = ...

or these:

let {x: x, y: y} = ... let {x: ?x, y: ?y} = ...

Pattern matching recursively decomposes the RHS and matches a (sub)value against the respective (sub)pattern of the LHS. Once you reach a variable, that submatch is unconditional, so a '?' doesn't change anything.

I see. Yes, I would be happy to get rid of any of those useless patterns.

# Brendan Eich (6 years ago)

Andreas Rossberg wrote:

On 11 July 2012 17:20, Russell Leggett <russell.leggett at gmail.com <mailto:russell.leggett at gmail.com>> wrote:

On Wed, Jul 11, 2012 at 7:14 AM, Andreas Rossberg
<rossberg at google.com <mailto:rossberg at google.com>> wrote:

    AFAICS, '?' on a variable itself would always be redundant,
    because a variable pattern is irrefutable anyway.


So you're saying that even this should match in a refutable pattern:

   let [a,b,c] = [1,2];

I would expect that to fail, especially in a hypothetical pattern
matching construct.

That would fail because the array pattern is refutable. What I meant is something else, namely that there is no difference between these:

let x = ... let ?x = ...

or these:

let {x: x, y: y} = ... let {x: ?x, y: ?y} = ...

Pattern matching recursively decomposes the RHS and matches a (sub)value against the respective (sub)pattern of the LHS. Once you reach a variable, that submatch is unconditional, so a '?' doesn't change anything.

Thanks -- this recovers my forgotten reason for advocating prefix-? on property names, not binding identifiers (which occupy the value positions in object literals). I think it addresses Allen's objection about why ? is not available for all bindings, simple identifiers too. It is part of the pattern grammar for property names only.

For arrays, the just-so story of prefix-? having an implicit "0", "1", etc. name after it works well enough -- there's no difficulty computing indexes statically.

Russell asked about 'length' -- per a previous thread, I thought we agreed that one [[Get]] of 'length' would be done before matching if and only if the array literal contains a spread (covering rest, the dual for a pattern of spread in the array literal corresponding to the pattern).

# Russell Leggett (6 years ago)

Russell asked about 'length' -- per a previous thread, I thought we agreed that one [[Get]] of 'length' would be done before matching if and only if the array literal contains a spread (covering rest, the dual for a pattern of spread in the array literal corresponding to the pattern).

Yes, I saw this in the proposal algorithm. The reason I ask is because it seems odd to have an additional structural requirement in some cases but not others. Of course, I understand why, but it sort of makes it look like [] implies additional array-like structure in one case, but not in another.

# Brendan Eich (6 years ago)

Russell Leggett wrote:

Russell asked about 'length' -- per a previous thread, I thought
we agreed that one [[Get]] of 'length' would be done before
matching if and only if the array literal contains a spread
(covering rest, the dual for a pattern of spread in the array
literal corresponding to the pattern).

Yes, I saw this in the proposal algorithm. The reason I ask is because it seems odd to have an additional structural requirement in some cases but not others. Of course, I understand why, but it sort of makes it look like [] implies additional array-like structure in one case, but not in another.

You get what you ask for. Pay as you go. Buy by the yard.

Ok, out of clichés but I hope we aren't going to revisit and impose a mandatory [[Get]] of 'length' on all array destructuring forms. That is otiose and costly (potentially very).

The presence of ...rest is static syntax, so there's no runtime maybe/maybe-not hazard.

# Russell Leggett (6 years ago)

On Wed, Jul 11, 2012 at 1:06 PM, Brendan Eich <brendan at mozilla.org> wrote:

Russell Leggett wrote:

Russell asked about 'length' -- per a previous thread, I thought
we agreed that one [[Get]] of 'length' would be done before
matching if and only if the array literal contains a spread
(covering rest, the dual for a pattern of spread in the array
literal corresponding to the pattern).

Yes, I saw this in the proposal algorithm. The reason I ask is because it seems odd to have an additional structural requirement in some cases but not others. Of course, I understand why, but it sort of makes it look like [] implies additional array-like structure in one case, but not in another.

You get what you ask for. Pay as you go. Buy by the yard.

Ok, out of clichés but I hope we aren't going to revisit and impose a mandatory [[Get]] of 'length' on all array destructuring forms. That is otiose and costly (potentially very).

The presence of ...rest is static syntax, so there's no runtime maybe/maybe-not hazard.

It would not have to be a [[Get]] of 'length', just a check for its existence. Its no deal breaker for me, but it seemed a little inconsistent. We could also be more strict with an Array.isArray check, but that seems too strict. Anyway, just a thought.

# Brendan Eich (6 years ago)

Russell Leggett wrote:

On Wed, Jul 11, 2012 at 1:06 PM, Brendan Eich <brendan at mozilla.org <mailto:brendan at mozilla.org>> wrote:

Russell Leggett wrote:




        Russell asked about 'length' -- per a previous thread, I
    thought
        we agreed that one [[Get]] of 'length' would be done before
        matching if and only if the array literal contains a spread
        (covering rest, the dual for a pattern of spread in the array
        literal corresponding to the pattern).


    Yes, I saw this in the proposal algorithm. The reason I ask is
    because it seems odd to have an additional structural
    requirement in some cases but not others. Of course, I
    understand why, but it sort of makes it look like [] implies
    additional array-like structure in one case, but not in another.


You get what you ask for. Pay as you go. Buy by the yard.

Ok, out of clichés but I hope we aren't going to revisit and
impose a mandatory [[Get]] of 'length' on all array destructuring
forms. That is otiose and costly (potentially very).

The presence of ...rest is static syntax, so there's no runtime
maybe/maybe-not hazard.

It would not have to be a [[Get]] of 'length', just a check for its existence.

That can still run arbitrary code for a proxy.

Its no deal breaker for me, but it seemed a little inconsistent.

Remember the Sesame Street song, "one of these things is not like the other"? ;-)

[a, b, c] [a, b, ...c]

Special forms are special so semantics can vary according to the syntax.

We could also be more strict with an Array.isArray check, but that seems too strict. Anyway, just a thought.

Absolutely Array.isArray is too strict, or just wrong. Array patterns are for array-likes, not only for arrays.