if-scoped let

# Nick Krempel (10 years ago)

Couldn't find anything on this in the archives, but is there a proposal for:

if (let var = expr) {
  // var in scope
}
else {
  // var in scope
}
// var out of scope

i.e. shorthand for:

{
  let var = expr;
  if (var) {
    // ...
  }
  else {
    // ...
  }
}

Also:

switch (let var = expr) {
  case foo:
    // var in scope
  default:
    // var in scope
}
// var out of scope

...which has a similar expansion.

Similarly for "while" and "do...while": this would bring everything in line with the current "for" / "for in" / "for of".

This also matches what C++ allows, with the exception of "do...while" (in our case, it seems acceptable to allow "do...while" too - the value would simply be "undefined" on the first iteration).

("const" should also be OK in place of "let", at least for "if" and "switch".)

# Nick Krempel (10 years ago)

Slight correction: bring in line with "for in" and "for of" only - since the condition part of the "for" does not allow this currently.

# Brendan Eich (10 years ago)

Nick Krempel wrote:

Slight correction: bring in line with "for in" and "for of" only - since the condition part of the "for" does not allow this currently.

Right, and for (let...;;) has (consensus reconfirmed last meeting) a fresh let binding per iteration (and one for the pre-loop scope if there's a closure in the first part of the head that captures a let binding -- turns out Dart does the same thing).

It's too late for ES6, but if (let ...), while (let ...), and switch (let ...) seem unproblematic to consider for ES7. I like them, we've talked about them more "off" than "on" over the years, I'm not sure why they never gained a champion.

do {...} while (let ...); is troublesome, though -- the condition is at the bottom but the binding would be hoisted to the do. Yes, it can be made to work, but the return of hoisting, no TDZ, smells. I'd skip it.

# Erik Arvidsson (10 years ago)

I also miss these from C++, especially the if form. I also agree that we do not want the do-while form of this.

# Waldemar Horwat (10 years ago)

On 11/29/2013 08:29 AM, Nick Krempel wrote:

Couldn't find anything on this in the archives, but is there a proposal for:

if (let var = expr) {
   // var in scope
}
else {
   // var in scope
}
// var out of scope

I frequently use the C++ equivalent of this. Haven't proposed it as part of the scoping upgrades just to keep things small, but I don't have any good reason not to do something like this either. It kind of falls in the same bucket as &&=, ||=, and ^^.

# Brendan Eich (10 years ago)

I mailed Arv and he kindly offered to draft and champion a bite-sized strawman for ES7, to support if/while/switch(let). Yay!

# Mark S. Miller (10 years ago)

What's ^^ ?

# Waldemar Horwat (10 years ago)

On 12/03/2013 05:30 PM, Mark S. Miller wrote:

What's ^^?

a^^b would essentially be the same as !a!=!b except that it would return the actual truthy value if it returns true.

# Brendan Eich (10 years ago)

I have to say if (let x = ... ) { /* that x in scope here */ } is >>> ^^, if you get what I mean :-P.

# Olov Lassus (10 years ago)

2013/11/29 Nick Krempel <ndkrempel at google.com>

Couldn't find anything on this in the archives, but is there a proposal for:

if (let var = expr) {
  // var in scope
}

... ("const" should also be OK in place of "let", at least for "if" and "switch".)

Thanks for taking this to the list. I was meaning to do it after a discussion with Allen at Front-Trends earlier this year but never got around to it, partly because Allen (correctly) suggested that it was almost certainly too late for ES6. I back this also.

Another perspective of why this is a great feature: My ES6 programming is const-first, meaning I only use let for bindings that change and const for everything else. In practice over 90% of all my variables are const which is great because the let's that are in there really stand out. The unfortunate consequence of not being able to declare a variable inside the if-condition (for example) is that it forces const's to let's.

I wanted to do

if (const val = compute(something)) {
    // ...
}

but I had to do

let val;
if (val = compute(something)) {
    // ...
}

which is unfortunate not only because val leaks to the outer scope but also because let suggest that the binding mutates (which it technically does, but practically doesn't).

# Axel Rauschmayer (10 years ago)

On 04 Dec 2013, at 9:13 , Olov Lassus <olov.lassus at gmail.com> wrote:

I wanted to do

if (const val = compute(something)) {
    // ...
}

but I had to do

let val;
if (val = compute(something)) {
    // ...
}

which is unfortunate not only because val leaks to the outer scope but also because let suggest that the binding mutates (which it technically does, but practically doesn't).

I’m curious, why not:

const val = compute(something);
if (val) {
    // ...
}

One thing that makes me skeptical w.r.t. if: will this kind of in-statement declaration only be used for truthy/falsy tests? In loops (for-of, while), I find this kind of thing useful, for if-then-else, much less so.

# Andreas Rossberg (10 years ago)

On 4 December 2013 10:13, Olov Lassus <olov.lassus at gmail.com> wrote:

I wanted to do

if (const val = compute(something)) {
    // ...
}

but I had to do

let val;
if (val = compute(something)) {
    // ...
}

which is unfortunate not only because val leaks to the outer scope but also because let suggest that the binding mutates (which it technically does, but practically doesn't).

I don't understand. Why can't you do

const val = compute(something)
if (val) {
    // ...
}

instead? Or, if you also want to address the other half of your concern, use an additional block (which admittedly is more verbose).

# Brendan Eich (10 years ago)

Olov did write "because val leaks to the outer scope". That's a reason for the convenience of

if (let x = ...) { /* x in scope here */ }

(or const), vs.

{ let x = ...; if (x) { /* ... */ } }

Braces count, this is winning in C++.

# Andreas Rossberg (10 years ago)

Yes, see the last sentence of my reply. What I (and presumably Axel) was puzzled about is why Olov said that he was forced to use 'let' instead of 'const'.

# Brendan Eich (10 years ago)

I took him to mean "please support const or let" - for sure! :-)

# Andreas Rossberg (10 years ago)

To voice the con side, I'm not fond of binders in conditions because they depend on -- and thus encourage overuse of -- falsy/truthy values or other implicit-conversion-like techniques (and their metastasis in APIs). I have seen that harm overall clarity more than it helps, at least in C++.

# Nick Krempel (10 years ago)

Taking it further, a (probably controversial) suggestion would be to allow "let" and "const" to be expressions, enabling:

if ((let foo = getFoo()).isReady()) {
   // foo in scope
} else {
  // foo in scope
}
// foo not in scope

There would be some details about whether its value is a Reference and what to do with "let a = 1, b = 2" but probably the biggest issue would be parsing ambiguities?

Or in a different direction, could consider allowing ToBoolean conversions for objects to be overloaded.

Even without either of the two additions proposed above, I would still find the if/switch/while(let) construct useful. For example, often a value is known to be either an object or null (or undefined). (And if it had the value 0 due to a bug in my program, it's not clear whether I want the true branch or the false branch anyway: so I should just use an assert if I want to protect against this.)

# Nick Krempel (10 years ago)

I should also say that I agree with you that it is unfortunate that if getFoo() is a function returning either a number or undefined (say), you can't use the proposed if(let) syntax to distinguish undefined.

# Brendan Eich (10 years ago)

Nick Krempel wrote:

Taking it further, a (probably controversial) suggestion would be to allow "let" and "const" to be expressions, enabling:

if ((let foo = getFoo()).isReady()) {
   // foo in scope
} else {
  // foo in scope
}
// foo not in scope

There would be some details about whether its value is a Reference and what to do with "let a = 1, b = 2" but probably the biggest issue would be parsing ambiguities?

Yes, because let is not reserved in ES1-3, or ES5 non-strict, we can't recognize it in expressions, only at start of statement (and even then we propose to break any old code of the form let[x] = y; by interpreting that as a destructuring let binding using a single-element array pattern).

ES4 had let expressions (let (x = y, z = w) ...), but presumed opt-in versioning, thankfully dead with 1JS and never to return.

Or in a different direction, could consider allowing ToBoolean conversions for objects to be overloaded.

Best not to overload your proposals, first.

Also, we do not want ToBoolean overloaded on arbitrary objects. Value objects as I've shown in several talks, e.g.,

www.slideshare.net/BrendanEich/js-resp

do allow boolean test to be customized, but only for a value class

# Brendan Eich (10 years ago)

Nick Krempel wrote:

I should also say that I agree with you that it is unfortunate that if getFoo() is a function returning either a number or undefined (say), you can't use the proposed if(let) syntax to distinguish undefined.

Then you would have to write it out the long way, and use a block around the if to narrow the scope. No shorthand proposal can satisfy all use-cases, but I think it would be a mistake to add if(let) and not test truthiness.

Of course, we may not agree to add such a truthy-testing if(let). But let's have Arv's strawman fiirst!

# Olov Lassus (10 years ago)

2013/12/4 Andreas Rossberg <rossberg at google.com>

I don't understand. Why can't you do

const val = compute(something)
if (val) {
    // ...
}

(also Axel) Oops - yeah I sure could. consts are one honking great idea -- let's do more of those! That's all. :)

# Sean Silva (10 years ago)

On Tue, Dec 3, 2013 at 9:13 PM, Waldemar Horwat <waldemar at google.com> wrote:

a^^b would essentially be the same as !a!=!b except that it would return the actual truthy value if it returns true.

Those semantics are extremely error-prone. (("foo" ^^ "bar") ^^ "baz") !== ("foo" ^^ ("bar" ^^ "baz")), and I doubt anyone will remember which corresponds to "foo" ^^ "bar" ^^ "baz"?

Also, the syntax gives the impression that this is a short-circuiting operator since it is a "doubled bitwise operator" like || and &&, but it can't be short-circuiting.

# Brendan Eich (10 years ago)

Yup. This is way, Way lower priority than if(let|const).

# John Lenz (10 years ago)

Wouldn't waiting for es7 make this a breaking change?

# John Lenz (10 years ago)

Nm. It would be illegal in the current spec.