New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Destructuring declarations that bind nothing should probably be an early error #97

Closed
allenwb opened this Issue Oct 21, 2015 · 66 comments

Comments

Projects
None yet
@allenwb
Member

allenwb commented Oct 21, 2015

In the ES6 spec., there is no check that a destructuring let/const/var actually introduces any bindings.

For example:

let ;

is a syntax error, but (updated 10/22/15)

let { } = obj;

is valid syntax that does nothing (it doesn't add any new variable bindings).

There are reports that some ES6 developers are encountering bugs cause by complex destructing patterns that unintentionally don't introduce any bindings.

This seems like a design bug in ES6. It would be trivial to specify that this is an early syntax error.

In 13.3.1.1
LexicalBinding : BindingPattern Initializer
- It is a Syntax Error if the BoundNames of BindingPattern has no elements.

In 14.1.2
FormalParameter : * BindingElement*
- It is a Syntax Error if the BoundNames of BindingElement has no elements.

Note that this is a breaking change for any program that actually contains such empty binding patterns. So there is some risk to making this change.

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Oct 21, 2015

Member

On the surface fixing this bug seems like something we could get away with while there are no interoperable destructuring implementations. Nice find!

Member

bterlson commented Oct 21, 2015

On the surface fixing this bug seems like something we could get away with while there are no interoperable destructuring implementations. Nice find!

@nzakas

This comment has been minimized.

Show comment
Hide comment
@nzakas

nzakas Oct 21, 2015

Oops, just sent an email to esdiscuss before I saw this. Yay! :)

nzakas commented Oct 21, 2015

Oops, just sent an email to esdiscuss before I saw this. Yay! :)

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Oct 21, 2015

Member

@nzakas thanks much for pointing this out :) Definitely file bugs for any other issues you find!

Member

bterlson commented Oct 21, 2015

@nzakas thanks much for pointing this out :) Definitely file bugs for any other issues you find!

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Oct 22, 2015

Member

@allenwb what about non-binding forms (AssignmentPattern and friends)? Should [] = [] be an error? I say so...

Member

bterlson commented Oct 22, 2015

@allenwb what about non-binding forms (AssignmentPattern and friends)? Should [] = [] be an error? I say so...

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Oct 22, 2015

Member

I also note that let {} and let [] are syntax errors at least since the initializer is not optional.

Member

bterlson commented Oct 22, 2015

I also note that let {} and let [] are syntax errors at least since the initializer is not optional.

@getify

This comment has been minimized.

Show comment
Hide comment
@getify

getify Oct 22, 2015

Contributor

I have strong concerns about this proposal:

  1. I have on two occasions already had the need (during debugging, etc) to do things like this:

    var {
    // x,
    // y
    } = foo();

    ...where I comment out different parts of the destructuring declaration, and in some cases even comment out all of them.

    It would really suck if this practice of commenting them out during debugging and dev would work only while there was at least one left uncommented, but if I needed them all commented out, I'd have to re-arrange the code by commenting out the entire destructuring pattern part, but (for example) maybe still need to call the foo().

    In that scenario, this seems at best surprising and at worst rather hostile.

  2. What about array destructuring? Will this proposal disallow this?

    var [
    // a,
    // b
    ] = foo()

    ... in the same way? Or what about?

    var [ , , ] = foo()

    ...if you have a stateful iterator on an array arr, you may want to do:

    var [ , , ] = arr;
    
    // later
    var [x, y] = arr;

    ...where you're expecting the [ , , ] to basically advance the stateful iterator by two steps, such that x and y are assigned from positions 2 and 3, respectively.

It sounds nice to assume that { }, [ ] and [ , , ] destructuring patterns are mistakes by developers, but that's just not a safe assumption.

Since @nzakas has already landed a linter rule for eslint for catching these things, I think that is the better path. Let tooling catch/warn about these, but don't make the language prevent these potentially not-mistake cases.

Contributor

getify commented Oct 22, 2015

I have strong concerns about this proposal:

  1. I have on two occasions already had the need (during debugging, etc) to do things like this:

    var {
    // x,
    // y
    } = foo();

    ...where I comment out different parts of the destructuring declaration, and in some cases even comment out all of them.

    It would really suck if this practice of commenting them out during debugging and dev would work only while there was at least one left uncommented, but if I needed them all commented out, I'd have to re-arrange the code by commenting out the entire destructuring pattern part, but (for example) maybe still need to call the foo().

    In that scenario, this seems at best surprising and at worst rather hostile.

  2. What about array destructuring? Will this proposal disallow this?

    var [
    // a,
    // b
    ] = foo()

    ... in the same way? Or what about?

    var [ , , ] = foo()

    ...if you have a stateful iterator on an array arr, you may want to do:

    var [ , , ] = arr;
    
    // later
    var [x, y] = arr;

    ...where you're expecting the [ , , ] to basically advance the stateful iterator by two steps, such that x and y are assigned from positions 2 and 3, respectively.

It sounds nice to assume that { }, [ ] and [ , , ] destructuring patterns are mistakes by developers, but that's just not a safe assumption.

Since @nzakas has already landed a linter rule for eslint for catching these things, I think that is the better path. Let tooling catch/warn about these, but don't make the language prevent these potentially not-mistake cases.

@rossberg

This comment has been minimized.

Show comment
Hide comment
@rossberg

rossberg Oct 22, 2015

Member

I disagree with this proposal. It creates an irregularity and makes life harder for code generators, but also for people who want to comment out parts of a pattern. I don't think there is any real win either. Moreover, as Brian pointed out, destructuring without a RHS is an error already.

Member

rossberg commented Oct 22, 2015

I disagree with this proposal. It creates an irregularity and makes life harder for code generators, but also for people who want to comment out parts of a pattern. I don't think there is any real win either. Moreover, as Brian pointed out, destructuring without a RHS is an error already.

@nzakas

This comment has been minimized.

Show comment
Hide comment
@nzakas

nzakas Oct 22, 2015

I think [] = [] should definitely be an error.

Another scary pattern is this:

let { loc: [] } = foo;

@rossberg-chromium this isn't just about variable declarations, there are also assignments. You'd still be able to comment out parts of a pattern, just not every part at once. You're also missing this use case completely:

let { foo: {} } = bar;

@getify I'm having a hard time understanding why commenting out every property at once is useful for debugging. Isn't this:

var {
// x,
// y
} = foo();

The same as:

// var {
// x,
// y
// } = foo();

?

nzakas commented Oct 22, 2015

I think [] = [] should definitely be an error.

Another scary pattern is this:

let { loc: [] } = foo;

@rossberg-chromium this isn't just about variable declarations, there are also assignments. You'd still be able to comment out parts of a pattern, just not every part at once. You're also missing this use case completely:

let { foo: {} } = bar;

@getify I'm having a hard time understanding why commenting out every property at once is useful for debugging. Isn't this:

var {
// x,
// y
} = foo();

The same as:

// var {
// x,
// y
// } = foo();

?

@caitp

This comment has been minimized.

Show comment
Hide comment
@caitp

caitp Oct 22, 2015

Contributor

Isn't this:

var {
// x,
// y
} = foo();

The same as:

// var {
// x,
// y
// } = foo();

?

The RHS is evaluated before the binding pattern is walked, so foo() is still invoked, regardless of whether any bindings are created

Contributor

caitp commented Oct 22, 2015

Isn't this:

var {
// x,
// y
} = foo();

The same as:

// var {
// x,
// y
// } = foo();

?

The RHS is evaluated before the binding pattern is walked, so foo() is still invoked, regardless of whether any bindings are created

@nzakas

This comment has been minimized.

Show comment
Hide comment
@nzakas

nzakas Oct 22, 2015

@caitp yes, but unless you're relying on side effects, that difference is inconsequential.

nzakas commented Oct 22, 2015

@caitp yes, but unless you're relying on side effects, that difference is inconsequential.

@nzakas

This comment has been minimized.

Show comment
Hide comment
@nzakas

nzakas Oct 22, 2015

Assuming goes both ways. :)

In any event, inability to comment out code just the way you want it doesn't seem like it should be a blocker for this. We all have editors, most have shortcut keys for inserting comments. If you need to use two keystrokes instead of one to help prevent unnecessary errors that are hard to find, that seems like a good tradeoff (IMHO). You can always move the RHS of an assignment to the next line temporarily and get the same effect.

nzakas commented Oct 22, 2015

Assuming goes both ways. :)

In any event, inability to comment out code just the way you want it doesn't seem like it should be a blocker for this. We all have editors, most have shortcut keys for inserting comments. If you need to use two keystrokes instead of one to help prevent unnecessary errors that are hard to find, that seems like a good tradeoff (IMHO). You can always move the RHS of an assignment to the next line temporarily and get the same effect.

@getify

This comment has been minimized.

Show comment
Hide comment
@getify

getify Oct 22, 2015

Contributor

intentional side effects sometimes are as important but yet benign as console.log statements inside foo(), again for debugging purposes.

Contributor

getify commented Oct 22, 2015

intentional side effects sometimes are as important but yet benign as console.log statements inside foo(), again for debugging purposes.

@jeffmo

This comment has been minimized.

Show comment
Hide comment
@jeffmo

jeffmo Oct 22, 2015

Member

There's always...

/*var {
// x,
y
} =*/ foo();

Clarifying edit: I don't feel strongly about this, but I also don't think that changes to the ability to single-line-comment at dev time for some rare set of cases is all that compelling a reason not to do this. I mean if this is really an issue, what do you do when some code does var {x} = foo();? Or even just var x = foo();?

Member

jeffmo commented Oct 22, 2015

There's always...

/*var {
// x,
y
} =*/ foo();

Clarifying edit: I don't feel strongly about this, but I also don't think that changes to the ability to single-line-comment at dev time for some rare set of cases is all that compelling a reason not to do this. I mean if this is really an issue, what do you do when some code does var {x} = foo();? Or even just var x = foo();?

@getify

This comment has been minimized.

Show comment
Hide comment
@getify

getify Oct 22, 2015

Contributor

you can always move the

yes of course you can. but my point is I shouldn't have to.

you make the one-sided assumption that my case ought to require me to type/edit more. i'll reiterate my similarly one-sided assumption/assertion from earlier: if you want protection from this "mistake" in your code, you should have to use a smart tool to protect you. but don't force that assumption on the rest of us.

Contributor

getify commented Oct 22, 2015

you can always move the

yes of course you can. but my point is I shouldn't have to.

you make the one-sided assumption that my case ought to require me to type/edit more. i'll reiterate my similarly one-sided assumption/assertion from earlier: if you want protection from this "mistake" in your code, you should have to use a smart tool to protect you. but don't force that assumption on the rest of us.

@rwaldron

This comment has been minimized.

Show comment
Hide comment
@rwaldron

rwaldron Oct 22, 2015

Contributor

FWIW, I agree with @getify and @rossberg-chromium for the same reasons they've stated. Please don't take this as dog-piling, as I will gladly argue against this at the next meeting ;)

Contributor

rwaldron commented Oct 22, 2015

FWIW, I agree with @getify and @rossberg-chromium for the same reasons they've stated. Please don't take this as dog-piling, as I will gladly argue against this at the next meeting ;)

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Oct 22, 2015

Member

Can someone give a little more detail about how removing a binding is useful for debugging purposes? It seems unlikely that removing a binding would have an interesting effect on a program.

Also, it's clear this needs consensus!

Member

bterlson commented Oct 22, 2015

Can someone give a little more detail about how removing a binding is useful for debugging purposes? It seems unlikely that removing a binding would have an interesting effect on a program.

Also, it's clear this needs consensus!

@rwaldron

This comment has been minimized.

Show comment
Hide comment
@rwaldron

rwaldron Oct 22, 2015

Contributor

@bterlson, less about the specific thing you might do and more about the strange error you'll encounter when doing perfectly reasonable things during development/debugging. In fact, you might want to eliminate all bindings to observe the outcome—it doesn't really matter why, it's just something a developer might do, and I think the language doesn't need to prohibit it just because it can. This is the sort of thing that dev tools (i.e. linters) can handle—if the end developer wants to.

Contributor

rwaldron commented Oct 22, 2015

@bterlson, less about the specific thing you might do and more about the strange error you'll encounter when doing perfectly reasonable things during development/debugging. In fact, you might want to eliminate all bindings to observe the outcome—it doesn't really matter why, it's just something a developer might do, and I think the language doesn't need to prohibit it just because it can. This is the sort of thing that dev tools (i.e. linters) can handle—if the end developer wants to.

@getify

This comment has been minimized.

Show comment
Hide comment
@getify

getify Oct 22, 2015

Contributor

in one of my cases, the properties I was destructuring were getters. the side effects were noisy console.log statements that I wanted to avoid by simply commenting out the assignment item in the pattern.

Contributor

getify commented Oct 22, 2015

in one of my cases, the properties I was destructuring were getters. the side effects were noisy console.log statements that I wanted to avoid by simply commenting out the assignment item in the pattern.

@ljharb

This comment has been minimized.

Show comment
Hide comment
@ljharb

ljharb Oct 22, 2015

Member

When commenting out every destructuring property in an object destructuring, could you not just add a nonexistent garbage property, and still do your debugging without having to worry about whether "no destructuring patterns" are an error?

I think the real question here is, which is going to be more common: that an empty-bindings destructuring pattern is a developer mistake (worth noting that things like the TDZ attempt to address developer mistakes, and I think this is indeed the language's job), or, that an empty-bindings destructuring pattern is a temporary thing that an educated developer is doing for debugging?

I tend to believe that both the former case is more common, and that in the latter case, the developer will not have a practical problem avoiding this error during debugging.

@rwaldron @getify @rossberg-chromium, do you really think both that the former (a newbie developer making a mistake) is far less common than a developer needing to debug destructuring with things that have side effects such that they need to comment out every destructuring binding, and that discontinuing to allow the latter would be excessively burdensome on an experienced developer during their debugging?

Member

ljharb commented Oct 22, 2015

When commenting out every destructuring property in an object destructuring, could you not just add a nonexistent garbage property, and still do your debugging without having to worry about whether "no destructuring patterns" are an error?

I think the real question here is, which is going to be more common: that an empty-bindings destructuring pattern is a developer mistake (worth noting that things like the TDZ attempt to address developer mistakes, and I think this is indeed the language's job), or, that an empty-bindings destructuring pattern is a temporary thing that an educated developer is doing for debugging?

I tend to believe that both the former case is more common, and that in the latter case, the developer will not have a practical problem avoiding this error during debugging.

@rwaldron @getify @rossberg-chromium, do you really think both that the former (a newbie developer making a mistake) is far less common than a developer needing to debug destructuring with things that have side effects such that they need to comment out every destructuring binding, and that discontinuing to allow the latter would be excessively burdensome on an experienced developer during their debugging?

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Oct 22, 2015

Member

@rwaldron I'm not arguing this should be prohibited just because it's possible to prohibit. I see it as a mistake people might appreciate an error for like omitting other BindingIdentifiers. The error seems especially hard to spot in nested forms, and with initializers it gets harder still. But if this isn't a valuable error then I agree no reason to throw it.

Member

bterlson commented Oct 22, 2015

@rwaldron I'm not arguing this should be prohibited just because it's possible to prohibit. I see it as a mistake people might appreciate an error for like omitting other BindingIdentifiers. The error seems especially hard to spot in nested forms, and with initializers it gets harder still. But if this isn't a valuable error then I agree no reason to throw it.

@getify

This comment has been minimized.

Show comment
Hide comment
@getify

getify Oct 22, 2015

Contributor

@bterlson

if this isn't a valuable error then I agree no reason to throw it.

that's not really the question, IMO. is it useful? it seems likely it might be useful to some people in some cases. but it's also something that clearly it's something that linting tools can easily check for, giving developers an opt-in for that checking, rather than locking developers into it by language design.

I think adding a lock-out to the language design deserves a higher bar than "useful/valuable error to some" (or even "many").

Contributor

getify commented Oct 22, 2015

@bterlson

if this isn't a valuable error then I agree no reason to throw it.

that's not really the question, IMO. is it useful? it seems likely it might be useful to some people in some cases. but it's also something that clearly it's something that linting tools can easily check for, giving developers an opt-in for that checking, rather than locking developers into it by language design.

I think adding a lock-out to the language design deserves a higher bar than "useful/valuable error to some" (or even "many").

@getify

This comment has been minimized.

Show comment
Hide comment
@getify

getify Oct 22, 2015

Contributor

@ljharb i don't think it's fruitful to have a guessing-match at who's got the corner on more common. it's clear what you and nicholas think, but i don't agree. shrugs.

however, no one seems to be addressing my second use-case above... the [ , , ] array-destructuring used to advance the cursor of a persistently-stateful generator. I suppose you'll also claim "very few will ever do that".

so i guess my only counter-argument is that this proposal has only presented one kind of use-case where the mistake happens (nested destructuring where they typed : instead of =). and i've presented two counter-arguments from what it unduly restricts.

Contributor

getify commented Oct 22, 2015

@ljharb i don't think it's fruitful to have a guessing-match at who's got the corner on more common. it's clear what you and nicholas think, but i don't agree. shrugs.

however, no one seems to be addressing my second use-case above... the [ , , ] array-destructuring used to advance the cursor of a persistently-stateful generator. I suppose you'll also claim "very few will ever do that".

so i guess my only counter-argument is that this proposal has only presented one kind of use-case where the mistake happens (nested destructuring where they typed : instead of =). and i've presented two counter-arguments from what it unduly restricts.

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Oct 22, 2015

Member

You can use linting tools to find errors, but I believe some errors are helpful enough to throw anyway. Regardless, I am not strongly in favor of making this an error (we allow many empty braces in various places already). I can see the rationale for keeping the current semantics.

Member

bterlson commented Oct 22, 2015

You can use linting tools to find errors, but I believe some errors are helpful enough to throw anyway. Regardless, I am not strongly in favor of making this an error (we allow many empty braces in various places already). I can see the rationale for keeping the current semantics.

@anba

This comment has been minimized.

Show comment
Hide comment
@anba

anba Oct 22, 2015

Contributor

Similar to @getify's [ , , ] example for destructuring assignment, I've also already used [...[]] = iterable to drain an iterable. IMO a quite handy shortcut when working on the REPL compared to the more verbose for (let ignored of iterable) ;.

Contributor

anba commented Oct 22, 2015

Similar to @getify's [ , , ] example for destructuring assignment, I've also already used [...[]] = iterable to drain an iterable. IMO a quite handy shortcut when working on the REPL compared to the more verbose for (let ignored of iterable) ;.

@rossberg

This comment has been minimized.

Show comment
Hide comment
@rossberg

rossberg Oct 22, 2015

Member

@ljharb:

When commenting out every destructuring property in an object
destructuring, could you not just add a nonexistent garbage property, and
still do your debugging without having to worry about whether "no
destructuring patterns" are an error?

Sure, but why require such extra tedium?

I think the real question here is, which is going to be more common: that
an empty-bindings destructuring pattern is a developer mistake (worth
noting that things like the TDZ attempt to address developer mistakes, and
I think this is indeed the language's job), or, that an empty-bindings
destructuring pattern is a temporary thing that an educated developer is
doing for debugging?

Aside: TDZ is not just to prevent mistake, but to have meaningful behaviour
for something like const declarations, and to be future-proof regarding
other possible extensions to declarations (e.g. guards were under
discussion at the time).

In most other places, JavaScript consciously does not help the programmer
avoiding mistakes, quite the opposite in fact.
Special case rules and irregularities are generally bad -- principle of
least surprise, refactoring, compositionality, etc. Before introducing
another such case I'd like to see strong evidence that it actually
addresses a real problem, i.e., that the supposed mistake is a common (and
difficult to diagnose) error in practice. I find that very hard to believe.

Member

rossberg commented Oct 22, 2015

@ljharb:

When commenting out every destructuring property in an object
destructuring, could you not just add a nonexistent garbage property, and
still do your debugging without having to worry about whether "no
destructuring patterns" are an error?

Sure, but why require such extra tedium?

I think the real question here is, which is going to be more common: that
an empty-bindings destructuring pattern is a developer mistake (worth
noting that things like the TDZ attempt to address developer mistakes, and
I think this is indeed the language's job), or, that an empty-bindings
destructuring pattern is a temporary thing that an educated developer is
doing for debugging?

Aside: TDZ is not just to prevent mistake, but to have meaningful behaviour
for something like const declarations, and to be future-proof regarding
other possible extensions to declarations (e.g. guards were under
discussion at the time).

In most other places, JavaScript consciously does not help the programmer
avoiding mistakes, quite the opposite in fact.
Special case rules and irregularities are generally bad -- principle of
least surprise, refactoring, compositionality, etc. Before introducing
another such case I'd like to see strong evidence that it actually
addresses a real problem, i.e., that the supposed mistake is a common (and
difficult to diagnose) error in practice. I find that very hard to believe.

@jeffmo

This comment has been minimized.

Show comment
Hide comment
@jeffmo

jeffmo Oct 22, 2015

Member

So yea, the fact that empty destructuring itself can incur side effects (i.e. draining an iterator) now seems to me like a compelling reason to not do this.

Member

jeffmo commented Oct 22, 2015

So yea, the fact that empty destructuring itself can incur side effects (i.e. draining an iterator) now seems to me like a compelling reason to not do this.

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Oct 22, 2015

Member

I somewhat contest that the assignment case should allow no identifiers if the binding patterns don't. It's certainly not clear that this is the "proper" way to do it. Wouldn't the same arguments would apply to making the empty assignment forms an error as well. [...[]] = iter is a pretty neat pattern, though not often useful (and perhaps could be solved in other ways, eg. by allowing bindingless rest ala [...] = iter, or by saying forms that pull from iterators like rest and patterns with elisions are allowed to omit bindings).

Member

bterlson commented Oct 22, 2015

I somewhat contest that the assignment case should allow no identifiers if the binding patterns don't. It's certainly not clear that this is the "proper" way to do it. Wouldn't the same arguments would apply to making the empty assignment forms an error as well. [...[]] = iter is a pretty neat pattern, though not often useful (and perhaps could be solved in other ways, eg. by allowing bindingless rest ala [...] = iter, or by saying forms that pull from iterators like rest and patterns with elisions are allowed to omit bindings).

@mohsen1

This comment has been minimized.

Show comment
Hide comment
@mohsen1

mohsen1 Oct 22, 2015

What if I'm only triggering getters by let { bar: {} } = foo; for some reason?

'use strict';

let foo = {
    get bar() {
        console.log('...');
    }
}

let { bar: {} } = foo;

I agree that's a horrible code, just saying it can be valid...

mohsen1 commented Oct 22, 2015

What if I'm only triggering getters by let { bar: {} } = foo; for some reason?

'use strict';

let foo = {
    get bar() {
        console.log('...');
    }
}

let { bar: {} } = foo;

I agree that's a horrible code, just saying it can be valid...

@allenwb

This comment has been minimized.

Show comment
Hide comment
@allenwb

allenwb Oct 23, 2015

Member

@mohsen1

What if I'm only triggering getters by let { bar: {} } = foo; for some reason?

Loose the let (ecause you aren't declaring any variables) and just say:

{ bar: {} } = foo;
Member

allenwb commented Oct 23, 2015

@mohsen1

What if I'm only triggering getters by let { bar: {} } = foo; for some reason?

Loose the let (ecause you aren't declaring any variables) and just say:

{ bar: {} } = foo;
@nzakas

This comment has been minimized.

Show comment
Hide comment
@nzakas

nzakas Oct 23, 2015

I'm not sure I understand the distinction between declarations and assignments, the error case is the same in both. In the former, the intent is to create new bindings but you fail to do so, in the latter, the intent is to assign a value and you fail to do so.

And what about destructured function arguments? It seems like you can be bitten by this in numerous places, and I'm not sure I can rationalize why you'd get a safety net for one place but not others.

nzakas commented Oct 23, 2015

I'm not sure I understand the distinction between declarations and assignments, the error case is the same in both. In the former, the intent is to create new bindings but you fail to do so, in the latter, the intent is to assign a value and you fail to do so.

And what about destructured function arguments? It seems like you can be bitten by this in numerous places, and I'm not sure I can rationalize why you'd get a safety net for one place but not others.

@getify

This comment has been minimized.

Show comment
Hide comment
@getify

getify Oct 23, 2015

Contributor

@allenwb of course, that doesn't just work as cited, because then you'll have to remember to put the ( ) around the whole assignment...

...which is not only unnecessary but also invalid when the 'let' is present.

Contributor

getify commented Oct 23, 2015

@allenwb of course, that doesn't just work as cited, because then you'll have to remember to put the ( ) around the whole assignment...

...which is not only unnecessary but also invalid when the 'let' is present.

@allenwb

This comment has been minimized.

Show comment
Hide comment
@allenwb

allenwb Oct 23, 2015

Member

@getify oops, of course.
since it is just being evaluate for effect, maybe:

0,{ bar: {} } = foo;
Member

allenwb commented Oct 23, 2015

@getify oops, of course.
since it is just being evaluate for effect, maybe:

0,{ bar: {} } = foo;
@allenwb

This comment has been minimized.

Show comment
Hide comment
@allenwb

allenwb Oct 23, 2015

Member

@nzakas

I'm not sure I understand the distinction between declarations and assignments,

The big difference I see is that declarations have non-local effect upon the static meaning or validity of parts of a program. For example, a declaration can shadow a declaration that exists in an outer scope and reference in an inner scope. A declaration can also make other declaration invalid (duplicate names). An assignment pattern does not have such non-local static effects.

In my proposal at the top of this thread, I included a non-bindings check for destructuring function parameters.

Member

allenwb commented Oct 23, 2015

@nzakas

I'm not sure I understand the distinction between declarations and assignments,

The big difference I see is that declarations have non-local effect upon the static meaning or validity of parts of a program. For example, a declaration can shadow a declaration that exists in an outer scope and reference in an inner scope. A declaration can also make other declaration invalid (duplicate names). An assignment pattern does not have such non-local static effects.

In my proposal at the top of this thread, I included a non-bindings check for destructuring function parameters.

@rossberg

This comment has been minimized.

Show comment
Hide comment
@rossberg

rossberg Oct 23, 2015

Member

@allenwb, you can interpret the argument that some of us have made as: this change is potentially detrimental even to your criterion 1 ("All other things being equal, language should help humans write correct code."). I mentioned refactoring and principle of least surprise as reasons.

Member

rossberg commented Oct 23, 2015

@allenwb, you can interpret the argument that some of us have made as: this change is potentially detrimental even to your criterion 1 ("All other things being equal, language should help humans write correct code."). I mentioned refactoring and principle of least surprise as reasons.

@nzakas

This comment has been minimized.

Show comment
Hide comment
@nzakas

nzakas Oct 23, 2015

@allenwb of your guiding principle is writing correct code, the local vs. nonlocal effects should be secondary to the fact that the assignment didn't actually assign, shouldn't it?

@rossberg-chromium you're arguing that principle of least surprise says { bar: {} } = foo; not doing an assignment is not surprising?

nzakas commented Oct 23, 2015

@allenwb of your guiding principle is writing correct code, the local vs. nonlocal effects should be secondary to the fact that the assignment didn't actually assign, shouldn't it?

@rossberg-chromium you're arguing that principle of least surprise says { bar: {} } = foo; not doing an assignment is not surprising?

@rossberg

This comment has been minimized.

Show comment
Hide comment
@rossberg

rossberg Oct 23, 2015

Member

@nzakas, no, I don't see what's surprising about that (except for somebody who doesn't know destructuring). Destructuring binds 0..N variables, or assigns 0..N mutables. A needless discontinuity between 0 and 1..N is surprising (and annoying).

Member

rossberg commented Oct 23, 2015

@nzakas, no, I don't see what's surprising about that (except for somebody who doesn't know destructuring). Destructuring binds 0..N variables, or assigns 0..N mutables. A needless discontinuity between 0 and 1..N is surprising (and annoying).

@ljharb

This comment has been minimized.

Show comment
Hide comment
@ljharb

ljharb Oct 23, 2015

Member

I feel like many developers see destructuring assignment as simply sugar for assigning to variables - var/let/const only ever creates 1 binding, so 1..N for destructuring assignment seems like a very logical step. From my perspective, allowing zero bindings is the discontinuity.

Member

ljharb commented Oct 23, 2015

I feel like many developers see destructuring assignment as simply sugar for assigning to variables - var/let/const only ever creates 1 binding, so 1..N for destructuring assignment seems like a very logical step. From my perspective, allowing zero bindings is the discontinuity.

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Oct 23, 2015

Member

I too am curious if @rossberg-chromium would argue for making the binding identifier of let decls optional. It would allow binding 0..N variables and let you comment out a binding without commenting out its side-effecting initializer. Can leave it to linters to give you an error when you type let; if you want.

Member

bterlson commented Oct 23, 2015

I too am curious if @rossberg-chromium would argue for making the binding identifier of let decls optional. It would allow binding 0..N variables and let you comment out a binding without commenting out its side-effecting initializer. Can leave it to linters to give you an error when you type let; if you want.

@michaelficarra

This comment has been minimized.

Show comment
Hide comment
@michaelficarra

michaelficarra Oct 23, 2015

Member

@bterlson That's already a valid program.

Member

michaelficarra commented Oct 23, 2015

@bterlson That's already a valid program.

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Oct 23, 2015

Member

Riiight, although you'd likely get an error if let isn't declared. Still curious though - is that ambiguity the only reason the binding identifier isn't optional?

Member

bterlson commented Oct 23, 2015

Riiight, although you'd likely get an error if let isn't declared. Still curious though - is that ambiguity the only reason the binding identifier isn't optional?

@michaelficarra

This comment has been minimized.

Show comment
Hide comment
@michaelficarra

michaelficarra Oct 23, 2015

Member

If not for that ambiguity, I would be in favour of that proposal.

Member

michaelficarra commented Oct 23, 2015

If not for that ambiguity, I would be in favour of that proposal.

@mohsen1

This comment has been minimized.

Show comment
Hide comment
@mohsen1

mohsen1 Oct 23, 2015

@allenwb

Loose the let

I'm convinced. If I'm not declaring anything I should be warned that let is not doing anything there...

mohsen1 commented Oct 23, 2015

@allenwb

Loose the let

I'm convinced. If I'm not declaring anything I should be warned that let is not doing anything there...

@getify

This comment has been minimized.

Show comment
Hide comment
@getify

getify Oct 23, 2015

Contributor

I dunno if this is a guiding principle of JS's design or not, but I believe it should be: any desired feature (or in this case, error) should be implemented in the least-burdensome way to those it would adversely affect -- adverse effects should be minimized.

The proposal(s) here seek to shut out valid cases of usage by telling affected developers "just do something different". however, the main concern of this thread (mistake avoidance) could be handled in a less burdensome way by letting tools handle the checks.

It's basically zero-cost to use tools to warn on this mistake for developers that want it, since such support has already landed. By contrast, it's a non-zero cost to shift the burden to developers to find some other way of doing any of the use-cases mentioned earlier.

Contributor

getify commented Oct 23, 2015

I dunno if this is a guiding principle of JS's design or not, but I believe it should be: any desired feature (or in this case, error) should be implemented in the least-burdensome way to those it would adversely affect -- adverse effects should be minimized.

The proposal(s) here seek to shut out valid cases of usage by telling affected developers "just do something different". however, the main concern of this thread (mistake avoidance) could be handled in a less burdensome way by letting tools handle the checks.

It's basically zero-cost to use tools to warn on this mistake for developers that want it, since such support has already landed. By contrast, it's a non-zero cost to shift the burden to developers to find some other way of doing any of the use-cases mentioned earlier.

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Oct 23, 2015

Member

@getify obviously adverse effects and burdens on developers should be minimized. No one disagrees with that.

Member

bterlson commented Oct 23, 2015

@getify obviously adverse effects and burdens on developers should be minimized. No one disagrees with that.

@nzakas

This comment has been minimized.

Show comment
Hide comment
@nzakas

nzakas Oct 25, 2015

@getify the difference is that you're claiming error avoidance is the job of tools, this proposal says error avoidance is the job of the language. I'm also in the latter camp, as you cannot expect everyone to be using tools to flag all errors nor can you assume all tools designed to find errors will find all bad patterns.

nzakas commented Oct 25, 2015

@getify the difference is that you're claiming error avoidance is the job of tools, this proposal says error avoidance is the job of the language. I'm also in the latter camp, as you cannot expect everyone to be using tools to flag all errors nor can you assume all tools designed to find errors will find all bad patterns.

@getify

This comment has been minimized.

Show comment
Hide comment
@getify

getify Oct 25, 2015

Contributor

@nzakas I am not broadly decrying all error avoidance in language design. There's an awful lot of good errors that JS asserts.

This particular error avoidance doesn't just avoid errors (though you clearly see it only as errors). In a place where there's a tension between error avoidance and valid uses, I think that's a space where an argument can be made that tools can and should fill the gap, since tools are opt-in and configurable; language design is (largely) not.

Contributor

getify commented Oct 25, 2015

@nzakas I am not broadly decrying all error avoidance in language design. There's an awful lot of good errors that JS asserts.

This particular error avoidance doesn't just avoid errors (though you clearly see it only as errors). In a place where there's a tension between error avoidance and valid uses, I think that's a space where an argument can be made that tools can and should fill the gap, since tools are opt-in and configurable; language design is (largely) not.

@mandel59

This comment has been minimized.

Show comment
Hide comment
@mandel59

mandel59 Oct 26, 2015

Empty binding has no problem in semantics but just problematic when human write JS. How do you think about JS code generated by programs? If empty binding is not allowed, I would have to make more effort to eliminate empty binding. Empty code and dead code may be problematic, but I'd like empty or dead code detection to be optional rather than required.

mandel59 commented Oct 26, 2015

Empty binding has no problem in semantics but just problematic when human write JS. How do you think about JS code generated by programs? If empty binding is not allowed, I would have to make more effort to eliminate empty binding. Empty code and dead code may be problematic, but I'd like empty or dead code detection to be optional rather than required.

@allenwb

This comment has been minimized.

Show comment
Hide comment
@allenwb
Member

allenwb commented Oct 30, 2015

@benjamn

This comment has been minimized.

Show comment
Hide comment
@benjamn

benjamn Nov 17, 2015

Member

Though I agree with @getify and others that enforcing this proposal feels like the job of a linter and not the language itself, I'm worried that the current proposal will miss a large class of realistic mistakes, regardless of whether it is implemented by a linter or by the language.

The most common mistake of this kind that I've encountered in real code is not "declarations that bind nothing," but confusion over the proper syntax for default values.

A programmer who doesn't yet understand every detail of destructuring syntax could reasonably think that this code extracts the x and y properties from the result of f() and provides a default value of {} for the y property:

let { x, y: {} } = f();

Of course what the programmer really wants is

let { x, y = {} } = f();

and there is real value in warning about the first pattern.

However, if I understand this proposal, because the declaration at least binds x, there would be no warning for code like this. But it still feels like a mistake (in the spirit of this proposal) that the y sub-pattern doesn't bind anything.

Member

benjamn commented Nov 17, 2015

Though I agree with @getify and others that enforcing this proposal feels like the job of a linter and not the language itself, I'm worried that the current proposal will miss a large class of realistic mistakes, regardless of whether it is implemented by a linter or by the language.

The most common mistake of this kind that I've encountered in real code is not "declarations that bind nothing," but confusion over the proper syntax for default values.

A programmer who doesn't yet understand every detail of destructuring syntax could reasonably think that this code extracts the x and y properties from the result of f() and provides a default value of {} for the y property:

let { x, y: {} } = f();

Of course what the programmer really wants is

let { x, y = {} } = f();

and there is real value in warning about the first pattern.

However, if I understand this proposal, because the declaration at least binds x, there would be no warning for code like this. But it still feels like a mistake (in the spirit of this proposal) that the y sub-pattern doesn't bind anything.

@getify

This comment has been minimized.

Show comment
Hide comment
@getify

getify Nov 17, 2015

Contributor

As long as we're discussing potential mistakes that could potentially be caught "early" by tooling (or... ugh... the language), I personally think these are other common mistakes around destructuring:

let { x } = ..
let [ y ] = ..

Why? Because object destructuring and array destructuring don't have a guard built into them -- which I think they should have had! -- to handle when the r-value cannot be destructured.

There's some cases that could be spotted easily, like:

let { x } = null;
let [ y ] = undefined;

But obviously the r-value is not always statically knowable, like let { x } = foo(). There's no plausible syntactic solution I'm aware of to this particular form of the problem.

But in the nested destructuring case, there is. Consider:

let { x: { y } } = ..
let [ [ z ] ] = ..

In these cases, if in their respective r-values, x (or 0th element in the array) are not present, or are present but null or undefined, we know the destructuring assignments here will fail. Solution?

let { x: { y } = {} } = ..
let [ [ z ] = [] ] = ..

The default value of {} or [] prevents such errors. So, if we're talking about mistakes that can happen that we could have tooling/language-mechanics prevent, maybe we should consider enforcing that a nested object/array destructuring assignment must always have a declared default value (whatever it is, even the empty ones as shown).

Contributor

getify commented Nov 17, 2015

As long as we're discussing potential mistakes that could potentially be caught "early" by tooling (or... ugh... the language), I personally think these are other common mistakes around destructuring:

let { x } = ..
let [ y ] = ..

Why? Because object destructuring and array destructuring don't have a guard built into them -- which I think they should have had! -- to handle when the r-value cannot be destructured.

There's some cases that could be spotted easily, like:

let { x } = null;
let [ y ] = undefined;

But obviously the r-value is not always statically knowable, like let { x } = foo(). There's no plausible syntactic solution I'm aware of to this particular form of the problem.

But in the nested destructuring case, there is. Consider:

let { x: { y } } = ..
let [ [ z ] ] = ..

In these cases, if in their respective r-values, x (or 0th element in the array) are not present, or are present but null or undefined, we know the destructuring assignments here will fail. Solution?

let { x: { y } = {} } = ..
let [ [ z ] = [] ] = ..

The default value of {} or [] prevents such errors. So, if we're talking about mistakes that can happen that we could have tooling/language-mechanics prevent, maybe we should consider enforcing that a nested object/array destructuring assignment must always have a declared default value (whatever it is, even the empty ones as shown).

@benjamn

This comment has been minimized.

Show comment
Hide comment
@benjamn

benjamn Nov 17, 2015

Member

@getify Though you've phrased that as an additional proposal, I get the feeling what you really mean is that we shouldn't get started down the path of adding warnings to the language as we think of them, because there are lots of corner cases that might potentially indicate programming mistakes, and we don't have a good theoretical framework for capturing all of them a priori. It's fine for linters to accumulate rules in response to common errors programmers actually make, but it's inappropriate for the language to enforce an incomplete set of noisy warnings.

To make this argument more concrete, imagine specifying an error message for @getify's strawman rule about default values being required for nested objects/arrays. Can you really hope to word such a nuanced warning in a way that non-expert programmers can easily understand it? If not, the supposed motivation of helping the programmer avoid mistakes is greatly diminished. A linter, on the other hand, could link to a wiki page on its project website for further explanation, and it's acceptable for a linter to be wrong sometimes, because you opted into using it. It can even read a configuration file to determine what rules you care about enforcing. The language specification itself has none of these luxuries.

Member

benjamn commented Nov 17, 2015

@getify Though you've phrased that as an additional proposal, I get the feeling what you really mean is that we shouldn't get started down the path of adding warnings to the language as we think of them, because there are lots of corner cases that might potentially indicate programming mistakes, and we don't have a good theoretical framework for capturing all of them a priori. It's fine for linters to accumulate rules in response to common errors programmers actually make, but it's inappropriate for the language to enforce an incomplete set of noisy warnings.

To make this argument more concrete, imagine specifying an error message for @getify's strawman rule about default values being required for nested objects/arrays. Can you really hope to word such a nuanced warning in a way that non-expert programmers can easily understand it? If not, the supposed motivation of helping the programmer avoid mistakes is greatly diminished. A linter, on the other hand, could link to a wiki page on its project website for further explanation, and it's acceptable for a linter to be wrong sometimes, because you opted into using it. It can even read a configuration file to determine what rules you care about enforcing. The language specification itself has none of these luxuries.

@getify

This comment has been minimized.

Show comment
Hide comment
@getify

getify Nov 18, 2015

Contributor

@benjamn -- +1

Contributor

getify commented Nov 18, 2015

@benjamn -- +1

@bterlson

This comment has been minimized.

Show comment
Hide comment
@bterlson

bterlson Nov 18, 2015

Member

Closing this as there was not consensus for changing the existing semantics. Thanks everyone for all the input!!

Member

bterlson commented Nov 18, 2015

Closing this as there was not consensus for changing the existing semantics. Thanks everyone for all the input!!

@bterlson bterlson closed this Nov 18, 2015

@azu azu referenced this issue Jul 30, 2016

Open

Destructuring #113

0 of 1 task complete
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment