Proposal: if variable initialization

# Rodrigo (7 years ago)

Proposal: inline let/const statements to declare and initialize variables within if statements, so that temporary variables exist only within the if/else block scope.

Reason: limits variable scope to the block where really needed, in similar fashion to variables defined in for(;;) statements. This improves readability while reducing unnecessary variables roaming outside their needed block.

The syntax would be very similar to the for(;;) assignment/test pair:

if (let x = 100; x > 50) {
    console.log(x); // 100
}
console.log(x); // ReferenceError

// same for const
if( const x = foo(); typeof x === 'object' ) {
    //...
}

// the variable is available within any else block
// after its declaration
if (let x = foo(); x < 50) {
    console.log(x);  // y is not available here
} else if (let y = bar(); y > 0) {
    console.log(x, y);
} else {
    console.log(x, y);
}

Right now there isn't a way to limit a variable to the if block:

let x = 100;
if (x > 50) {
    console.log(x);
}
// x is in scope, but may not be needed beyond the if statement
console.log(x);

// or a non-strict assignment, which also "leaks" scope
if( (x = 100) > 50 ) {
    // ...
}

There are many "workarounds" available, here's a few:

// workaround 1: can be remedied with a scope block
// but it's asymmetrical and non-idiomatic
{
    let x = 100;
    if (x > 50) {
        console.log(x);
    }
}

// workaround 2: with a for statement
// but this is non-idiomatic, hard to read and error-prone
for (let x = 100; x > 50;) {
    console.log(x);
    break;
}

If-initialization is available in many languages (Go, Perl and Ruby come to mind) and are considered best practice in each one of them:

// Golang - x is defined, assigned and conditionally tested
if x := 100; x > 50 {
    // x is in scope here
} else {
    // and in here
}
// x is not available here

###### Perl
if( my $x = 100 ) {
    print $x;
}
print $x; # an error

if ( ( my $x = myfoo() ) > 50 ) {  # also ok in Perl
    print $x;
}

###### Ruby
if ( x = 100 )  # parens required per style guide
    puts(x)
end
puts(x) # unfortunately Ruby does not limit scope to if, so x "leaks"

I think this would be a great and important addition to the language.

-Rodrigo

PS: Just for the sake of comparison, Perl-style if-assignments could also be an option, albeit a very bad one IMO:

if( ( let x = 100 ) > 50 ) {
}

A Perl-style, value-returning let/const has readability issues, opens quite a few fronts and sort of implies that let/const can return values anywhere in the code outside if/else. On the other hand it would fit with the currently if assignment if( x = y ). Definitely not recommended.

# Jordan Harband (7 years ago)

Is the use case only ever to capture the thing that serves as the conditional?

If so, would perhaps something like if.value work better? Since it's a keyword, it could be made to only work in the if block, and you wouldn't need any of that odd multi-statement stuff in the conditional parens.

# Naveen Chawla (7 years ago)

What would if.value look like in an example?

Wouldn't it be possible to have something like if(const x = getX() && const y = getY()) to capture more than just the conditional if required? I'm guessing that would probably break something somewhere, but I'm not sure what.

# Naveen Chawla (7 years ago)

OK I neglected to read the original post fully. My last post example would be based on allowing const and let declarations to expressions in of themselves (in the case of multi variables, returning the last one). So let me ask, what exactly would be the problem with this?

# Rodrigo (7 years ago)

Here are my gripes with let and const returning values:

  1. declaration lists are hard to read:

    if ((let x = 10, y = 20) > 15) { // true, but what's being compared here? 10 or 20? (answer: 20) }

Although right now this is allowed and the last element is compared:

if ((x = 10, y = 20) > 15) {
    // result is true, 20 > 15
}
  1. Destructuring assignments are also confusing, what's being compared here?

    if(let [x,y] = [1,2]) { }

Again, this is allowed as of today:

if([x,y] = [1,2]) {
    // true, as it returns [1,2]
}
  1. Nesting let/const would be either expected everywhere (not only in the if) or a possible side effect from the implementation. Similar to languages such as Perl.

    let x = foo(let y = 100, z = 200); // what's the scope of x and z?

This leads to hard to read and very confusing code golf.

That's why Golang went with something simple, if([declaration];[conditional]), and avoided confusion over := assignments returning values anywhere in the code. x:=( y:= 20 ) is not allowed in Go.

It expands on the 45-year tried-and-true structure of for(;;) to create if(;) and keep the ES language simple and clear expanding on its own concept of for(;;).

# Jordan Harband (7 years ago)
if (someComplicatedCondition()) {
  doSomeLogic();
  doSomeOtherLogic(if.value, true);
}
# kai zhu (7 years ago)

this is why let and const should never have been introduced. if we had stuck with just var, none of these petty-arguments and bickering among team-members/shops on scoping-styles that ultimately have zero productivity or benefit to web-projects would be possible.

and there's nothing wrong with pre-declaring all variables at the top-level of a function (which is an es5 best-practice that’s still valid today), regardless whether some are only used in conditional-blocks or not, like this real-world example [1]:

local.validateBySwaggerSchema = function (options) {
/*
 * this function will validate data against schema
 * http://json-schema.org/draft-04/json-schema-validation.html#rfc.section.5
 */
    var $ref,
        circularList,
        data,
        dataReadonlyRemove2,
        ii,
        oneOf,
        schema,
        test,
        tmp;
...
        // dereference schema.$ref
        $ref = schema && schema.$ref;
        if (!$ref) {
            break;
        }
...
    tmp = typeof data;
    if (tmp === 'object' && Array.isArray(data)) {
        tmp = 'array';
    }
...
    if (schema === local.swaggerSchemaJson.definitions.jsonReference) {
...
    }
...
};

[1] kaizhu256/node-swgg/blob/2018.2.1/lib.swgg.js#L4076, kaizhu256/node-swgg/blob/2018.2.1/lib.swgg.js#L4076

# Thomas Grainger (7 years ago)

Is this sarcastic?

# kai zhu (7 years ago)

@thomas, no. i'm serious in my opinion that let and const were mistakes.

# Mike Samuel (7 years ago)

On Tue, Mar 20, 2018 at 3:57 PM, Rodrigo <rodrigolive at gmail.com> wrote:

Proposal: inline let/const statements to declare and initialize variables within if statements, so that temporary variables exist only within the if/else block scope.

With setters you can get some oddities because the getter need not return the same value set.

const o = { get x() { return this.x_ }, set x(v) { return this.x_ = String(v) } } if (!(o.x = 0)) { console.log('o.x is falsey: ' + !o.x); }

If decorators tc39/proposal-decorators#51 are

allowed on let/const declarations, then you might get a similar source of confusion.

This might be especially confusing since in Java and C++ the result of an assignment is the value actually assigned after any type coercion (or a reference to the left).

In JavaScript, the result of an assignment is the result of the right operand. Though its a bit muddy, since the result of x++ is x coerced to a number for symmetry with x += 1.

If it turns out that decorators are widely used for type annotations on declarations and some do custom coercion on assignment, does that introduce potential problems?

# Rodrigo (7 years ago)

My proposal is to keep it simple and implement the if-assignment if( const o = ...; ! o ) { ... } mimicking the for(;;) assignment behavior.

That way we can scope a variable to the if block and we can do that separately from assignment.

Assigning and comparing at the same time opens up all sort of oddities as the variable transitions from lvalue to comparison operand. This already exists in the language and is definitely not good practice.

if.value, as proposed earlier is a bombshell similar to this, which could easily get scrambled with the introduction of additional if blocks.

The beauty of if(;) is that it makes scope blocks possible while avoiding the asymmetric behavior caused by assigning and comparing in one go.

    if( let people=getTeamArray(); people.length > 2 ) {
       console.log("it's a crowd", people.join(','));
   }
   else if( people.length == 2 ) {
       console.log("just a pair");
   }
   else if( people.length == 1 {
       console.log("solo");
   }
   else {
       console.log("none");
   }
# Mike Samuel (7 years ago)

On Wed, Mar 21, 2018 at 11:02 AM, Rodrigo <rodrigolive at gmail.com> wrote:

My proposal is to keep it simple and implement the if-assignment if( const o = ...; ! o ) { ... } mimicking the for(;;) assignment behavior.

Fair enough. If the assignment is separate from the condition, then none of that matters. That question only pertained to let/const declarations used for their value.

That way we can scope a variable to the if block and we can do that

separately from assignment.

Assigning and comparing at the same time opens up all sort of oddities as the variable transitions from lvalue to comparison operand. This already exists in the language and is definitely not good practice.

if.value, as proposed earlier is a bombshell similar to this, which could easily get scrambled with the introduction of additional if blocks.

The beauty of if(;) is that it makes scope blocks possible while avoiding the asymmetric behavior caused by assigning and comparing in one go.

    if( let people=getTeamArray(); people.length > 2 ) {
       console.log("it's a crowd", people.join(','));
   }
   else if( people.length == 2 ) {
       console.log("just a pair");
   }
   else if( people.length == 1 {
       console.log("solo");
   }
   else {
       console.log("none");
   }

IIUC, it sounds like the right side could be syntactic sugar for the left side. If that's right, what about the left side warrants new syntax to enable the right?

CurrentProposed

{ let people = getTeamArray(); if( people.length > 2 ) { console.log("it's a crowd", people.join(',')); } else if( people.length == 2 ) { console.log("just a pair"); } else if( people.length == 1 { console.log("solo"); } else { console.log("none"); } }

if( let people = getTeamArray(); people.length > 2 ) { console.log("it's a crowd", people.join(',')); } else if( people.length == 2 ) { console.log("just a pair"); } else if( people.length == 1 { console.log("solo"); } else { console.log("none"); }

I can imagine that it might be nice when used later in an else-if chain:

if (c0) { ... } else (let x = v1, c1(x)) { ... } else ...

vs

if (c0) { ... } else { let x = v1; if (c1(x)) { ... } else ... }

Can you point at any particularly tricky code that could be simplified by this?

# kai zhu (7 years ago)

how is any of this less-confusing than a simple style-guide of pre-declaring all variables @ the beginning of a function? again, there’s zero legitimate reason why javascript even needs block-level scoping of variables (and arguing because other languages have it is not a reason). you're just creating more needless javascript style-guide variance that further confuses your team-members / project-successor.

/*jslint
    bitwise: true,
    browser: true,
    maxerr: 8,
    maxlen: 100,
    node: true,
    nomen: true,
    regexp: true,
    stupid: true
*/
(function () {
    'use strict';
    // es5 best-practice of declaring all variables at beginning of function,
    // and runtime-checked by 'use strict' pragma
    var bar,
        c0,
        c1,
        foo,
        getTeamArray,
        people,
        v1,
        xx,
        yy;



    getTeamArray = function () {
        return [1, 2, 3];
    };
    people = getTeamArray();
    if (people.length > 2) {
        console.log("it's a crowd", people.join(','));
    } else if (people.length === 2) {
        console.log("just a pair");
    } else if (people.length === 1) {
        console.log("solo");
    } else {
        console.log("none");
    }
    // output: it's a crowd 1,2,3



    bar = function () {
        return 0;
    };
    foo = function () {
        return 50;
    };
    (function () {
        xx = foo();
        if (xx < 50) {
            console.log(xx); // yy is not available here
            return;
        }
        yy = bar();
        if (yy > 0) {
            console.log(yy);
            return;
        }
        // else
        console.log(xx, yy);
    }());
    // output: 50 0



    c0 = null;
    c1 = function (xx) {
        return xx;
    };
    v1 = false;
    // 'unset' xx
    xx = undefined;
    (function () {
        if (c0) {
            console.log(c0); // xx is not available here
            return;
        }
        xx = v1;
        if (c1(xx)) {
            console.log(xx);
            return;
        }
        // else
        console.log(c0, xx);
    }());
    // output: null false
}());
# Sebastian Malton (7 years ago)

An HTML attachment was scrubbed... URL: esdiscuss/attachments/20180321/66c2555e/attachment-0001

# Christopher Thorn (7 years ago)

This is just trolling that seems intended to derail the discussion.

# Jerry Schulteis (7 years ago)

I have used the desugared version and found that what seemed obvious to me, that the purpose of the block was to limit the scope, was not obvious to other developers on  my team. If this proposal moves forward I think it should include switch and while in addition to if.

On Wednesday, March 21, 2018, 11:56:08 AM CDT, Mike Samuel <mikesamuel at gmail.com> wrote:  

[...]IIUC, it sounds like the right side could be syntactic sugar for the left side.If that's right, what about the left side warrants new syntax to enable the right?

| Current | Proposed | | { let people = getTeamArray(); if( people.length > 2 ) { console.log("it's a crowd", people.join(',')); } else if( people.length == 2 ) { console.log("just a pair"); } else if( people.length == 1 { console.log("solo"); } else { console.log("none"); } } | if( let people = getTeamArray(); people.length > 2 ) { console.log("it's a crowd", people.join(',')); } else if( people.length == 2 ) { console.log("just a pair"); } else if( people.length == 1 { console.log("solo"); } else { console.log("none"); } |

[...]

# kai zhu (7 years ago)

i'm not trolling. unlike javascript, most other languages deal near exclusively with blocking-code, which is only applicable to low-level library-code in javascript (that pretty much anyone can write). at higher-level integration-stuff dealing with non-blocking io, these blocking-code design-patterns from other languages are pretty-much useless. like say … block-level scoping, because it doesn’t mix well with closures, which are heavily used in integration-level javascript to keep track of states for non-blocking code.

e.g.

/*jslint
    bitwise: true,
    browser: true,
    maxerr: 8,
    maxlen: 200,
    node: true,
    nomen: true,
    regexp: true,
    stupid: true
*/
(function () {
    'use strict';

    var chunkList, request, response, timerTimeout;
    chunkList = [];
    // auto-timeout and cleanup closure-vars request and response after 30000 ms
    timerTimeout = setTimeout(function () {
        try {
            request.destroy();
        } catch (ignore) {
        }
        try {
            response.destroy();
        } catch (ignore) {
        }
        console.error('timeout error after 30000 ms');
    }, 30000);
    request = require('https').request(
        require('url').parse('https://www.example.com'),
        function (_response) {
            response = _response;
            response.on('data', function (chunk) {
                // append chunk to closure-var chunkList
                chunkList.push(chunk);
            });
            response.on('end', function () {
                // print closure-var chunkList to stdout
                console.log(Buffer.concat(chunkList).toString());
                // cancel closure-var timerTimeout,
                // after request successfully completes
                clearTimeout(timerTimeout);
            });
        }
    );
    request.end();



/*
output:

<!doctype html>
<html>
<head>
    <title>Example Domain</title>
...
</head>

<body>
<div>
    <h1>Example Domain</h1>
    <p>This domain is established to be used for illustrative examples in documents. You may use this
    domain in examples without prior coordination or asking for permission.</p>
    <p><a href="http://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>
*/
}());
# Mike Samuel (7 years ago)

On Wed, Mar 21, 2018 at 1:27 PM, Sebastian Malton <sebastian at malton.name>

wrote:

Because block-level scoping is a very good way to avoid certain bugs and is easier to reason about. Especially when considering project successors.

+1. function-scoped variables in loop bodies caused tons of bugs before let-scoped variables and were a main motivating case.

var i; for (i = 0; i < arr.length; ++i) { f(function () { /* Do something with */ arr[i]; }); }

vs

for (let i = 0; i < arr.length; ++i) { f(function () { /* Do something with */ arr[i]; }); }

Yes, linters got pretty good at finding uses of closed-over variables modified in a loop, but the workarounds were not ideal.

var i; for (i = 0; i < arr.length; ++i) { f(function (i) { return function () { /* Do something with */ arr[i]; } }(i)); }

Block scoping is just better for code that uses loops, variables, and function expressions.

# Mike Samuel (7 years ago)

TIL

On Wed, Mar 21, 2018 at 2:29 PM, kai zhu <kaizhu256 at gmail.com> wrote:

/*jslint

stupid: true

*/

# kai zhu (7 years ago)

@mike yes that’s true, but issues with blocking-code javascript data-structures/algorithms are rarely the reason web-projects fail. they fail largely due to unresolvable integration-level io/workflow issues (that are unique only to javascript). block-scoping fixes the insignificant former, while exacerbating the more pressing latter, by encouraging people to pollute variable-declarations all-over-the-place; making it harder to discern-and-debug which ones are critical io-related closure-variables and which ones are not.

data-structure and algorithmic coding-skills are rarely all that useful in javascript (just throw sqlite3 at it and it will likely go away). integration-level debugging-skills (and knowledge of which coding design-patterns to employ to make io easier to debug) are far more valuable and correlable to successfully shipping a web-project.

# Isiah Meadows (7 years ago)

I'm personally very much for this if (var ...; cond) { ... } syntax. I couldn't tell you how many times I would've liked something to that effect, since that's probably one of my biggest areas of boilerplate.

I would also be in favor of if (var ...) { ... } as a shorthand that guards != null the expression result (pre-match), since that's about 90% of my use cases for it. There is a potential area of ambiguity in sloppy for if ( let [ x ] = y ), since that would be currently parsed as var tmp = y; let[x] = tmp; if (tmp) { ... }, but I doubt breaking that would be of much web compat risk. (A similar ambiguity existed with for (let [, but that break didn't cause many issues.)

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Sebastian Malton (7 years ago)

Sorry if I missed a message but would such an initialization be only available in the first if block or also in the subsequent else if and else blocks?

Sebastian Malton

Original Message   From: isiahmeadows at gmail.com Sent: March 21, 2018 6:18 PM To: mikesamuel at gmail.com Cc: sebastian at malton.name; es-discuss at mozilla.org Subject: Re: Proposal: if variable initialization

I'm personally very much for this if (var ...; cond) { ... } syntax. I couldn't tell you how many times I would've liked something to that effect, since that's probably one of my biggest areas of boilerplate.

I would also be in favor of if (var ...) { ... } as a shorthand that guards != null the expression result (pre-match), since that's about 90% of my use cases for it. There is a potential area of ambiguity in sloppy for if ( let [ x ] = y ), since that would be currently parsed as var tmp = y; let[x] = tmp; if (tmp) { ... }, but I doubt breaking that would be of much web compat risk. (A similar ambiguity existed with for (let [, but that break didn't cause many issues.)

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Isiah Meadows (7 years ago)

My implication was that it'd only be available in the if (if declared with let/const).

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Isiah Meadows (7 years ago)

Fun fact: you'd be surprised how many non-trivial JS stuff there is. If you've ever used jQuery selectors, you've used a selector engine implemented in JS 1, which uses quite a few advanced data structure and algorithm techniques. Not all JavaScript is inherently async, nor does it need to be. Updating the UI in larger web apps can't be easily done synchronously, and it requires significant familiarity with data structures. The alternative is to do it all server side, but then you're wasting resources doing what your client could do just as easily - it doesn't necessarily have all the data, but it certainly has a working CPU and RAM chip. At smaller scale like with typical business websites, it doesn't matter, but at even mid-level startup scale, it drives server costs down quite a bit. (If you can move 15% of the RAM/CPU workload to the client without affecting perceptible performance, that's 15% fewer servers/data clusters you need. For Facebook and Google, that could mean one less data center they have to build and maintain.)

Also, little language annoyances are themselves worth tackling as real problems. Programming languages aren't just made to solve problems. They are also themselves an art form unto themselves.


Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Rodrigo (7 years ago)

Not just let-scopes, but the introduction of async/await also welcomes the introduction of if-scoped variables.

if (const data = await collection.find({}).toArray(); data.length > 10)

{ console.log(data); } else if (data.length > 0) { console.log(data); } else { console.log(data); }

And, as mentioned by @jerry, this can be extended to switch and while. Golang has switch(;) initialization too afaik.

switch( const today = new Date(); today.getDay() ) {
     case 0:
        console.log( "Don't work on %s", today.toString() );
        break;
}

while would be a bit unnecessary, due to the fact that it can be replicated with for( <assign>; <expression>; ), but could be

available for consistency with if and switch.

El mié., 21 mar. 2018 19:47, Mike Samuel <mikesamuel at gmail.com> escribió:

# Isiah Meadows (7 years ago)

<concern>

I get the nagging feeling someone is eventually going to complain that this feature is unnecessary and smells too much like let blocks:

It seems odd to extend it to switch, especially with an added condition like that. It seems odd to do something that's already easily accomplished with just an extra newline, with hardly any more typing:

const today = new Date()
switch (today.getDay()) {
    // ...
}

(The if examples don't have this same issue, BTW.)

One statement I'd like to see this extended to is while, but it's partially duplicative of the C-style for (so it kind of feels wrong to add in a way). Also, it doesn't make sense to add for do { ... } while (...), since the condition is after the block. So it seems this only really makes sense for if.

I do have one other related thing I'd like to see: add a let foo = expr() else { ... } variant, with a line terminator restriction before the else so it can't be confused with an else within an if.


Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Rodrigo (7 years ago)

I agree, the switch statement was not on my radar and I don't see that need, it's the scoped if constructs that would make code cleaner.

# Mike Samuel (7 years ago)

On Thu, Mar 22, 2018 at 3:50 AM, Isiah Meadows <isiahmeadows at gmail.com>

wrote:

I do have one other related thing I'd like to see: add a let foo = expr() else { ... } variant, with a line terminator restriction before the else so it can't be confused with an else within an if.

Making it a restricted production would solve the grammatical ambiguity for existing code, but maybe in an errorprone way for future code:

if (c) let foo = expr() else { ... } // else attaches to let
if (c) let foo = expr(); else { ... } // else attaches to if

Would the semantics differ from

let foo = expr() || ({} => { ... })()

?

# Michael Luder-Rosefield (7 years ago)

That strikes me as territory the 'do expression' proposal tc39/proposal-do-expressions is more fitted for:

const x = do { if (c) expr; else { ... } };

What I'd like for this proposal is something that works consistently and obviously for all blocks with a parenthesised element to them. When they're formally separated by semi-colons, as in for (a;b;c), each of a,b,c acts as an expression. Why not allow any of those expressions to be replaced by a statement block that acts like a do expression, each of which's scope is nested under the previous one and are available to the following block?

That didn't come out very clearly, so let's try with an example:

for ({ let x = 1, y = 2; console.log("I'll be printed every loop!"); }; { let s = 'some string'; if (y%7 === 0) x === y; else x < 1000; }; { let s = 'some other string'; x+=1; if (y%3 === 0) y += 2; else y += 1; }) { // whatever code here // local scope hierarchy is // { // x, // y, // SCOPE: { // s: 'some string', // SCOPE: { // s: 'some other string' // } // } // } }

I'm just using some random logic in the blocks to illustrate the point: all the variables declared in the blocks are accessible in the for block, but the 'some string' s is masked by the 'some other string' s in the child scope. The termination condition in the second block can vary each loop, as can the iteration operation in the last block, and is simply the last value in the block as-per do expressions.

# Isiah Meadows (7 years ago)

Probably true, more so than the if (var ...)/etc. (which can't be as easily desugared). My else variant desugars more to something that is also easily simulated, and it's a less common case:

let foo = bar else return baz;

// Desugared
let _tmp = bar;
if (tmp == null) return baz;
let foo = _tmp;

In this case, there's also the question of whether to require a return in all code paths, which probably makes this a bit more complicated than what would be worth for such a simple language feature.

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Naveen Chawla (7 years ago)

I'm still not seeing a compelling case for not allowing const / let declarations to be evaluated as expressions. Or I've missed it.

As was noted,

if(x = 5) is already allowed.

Is if(const x = 5) really that much of a stretch?

To answer a concern about a function call like myFunction(const x = 7), of course the scope of x would be where it is declared. It can't be anywhere else (like inside myFunction or something).

# kai zhu (7 years ago)

unlike all other popular c-derivatives, javascript is the only one that's not a blocking-code language by design. maybe tc39 should do some outreach to educate the many language-designers and polyglots who only know blocking-code patterns of this simple fact.

as i've said before, adding these foreign, blocking-code design-patterns from java/c#/python/c++ can only get you so far with insignificant, low-level library-code, which is not what javascript is about. javascript is about integrating non-blocking io and workflows, so that browser/webview UX’s don’t block-and-freeze and give the appearance a webapp has crashed.

but async/await will save us! …no it will not. the hard-part of integration-level javascript is not writing non-blocking code, but debugging non-blocking code. and debugging non-blocking generator-magic (and promise-magic), is generally more difficult than debugging magic-less recursive-callbacks.

there is currently an industry-glut of so-called “senior” javascript-developers, who only know how to write low-level library-code (and bikeshed them to death with let, const, destructuring, etc...), but are clueless on how to integrate whatever-it-is-they-wrote into a shippable web-product. no, they usually need an “intermediate” frontend-developer to do that (who oftentimes didn’t have formal cs-training and wasn't brainwashed with all that blocking-code cr*p that hinders the "senior” developer from carrying out integration-work).

# Rodrigo (7 years ago)

@Naveen, I think it's best to avoid this road altogether and keep initialization clean, even though assignment isn't.

The proposal is that given for(;;) hence if(;) instead of given x=y hence let x=y.

But yes, if( x = 5) is already allowed, but it's confusing and hard to read.

Confusing on what is being compared on multiple statements: if((const x = 5, y = 10) > 0)

Confusing when destructuring, on what's being compared: if(let [x,y] = [1,2])

Confusing when multiple statements: if ((x = 10, y = 20) > 15)

Looks like an error: if( x = 5 ) versus if( x == 5), did the programmer forget the =?

And if you introduce nesting initializations everywhere outside the if, that's basically an invitation for readability nightmare. let and const anywhere introduce 2 conflicting best-practices:

  • Rule 1: declare your variables that are used exclusively within if blocks within the if parens

  • Rule 2: don't declare variables within another statement (so that people will refrain from doing foo( let x=10 )

Consider that, historically, other languages such as Perl allowed if( (my $x = 1) > 0) and foo( my $x = 100) and became the ugly child of

power programming; whereas Golang, born much later, has limited initializations to things such as if( x:=1; x > 0) and has kept

things quite minimalistic (and clear for the programmer).

# realworld example, hard to find variable declaration:

$redis->subscribe( 'queue', my $callback = sub {
    ...
});
# Naveen Chawla (7 years ago)

I don't know why foo(let x = 10) would be a bad practice or hurt readability.

I find it perfectly readable and with obvious meaning!

    foo(const x = 10)
    bar(x)

vs

    const x = 10
    foo(x)
    bar(x)

I also find it "clean". So I guess these aren't really useful terms.

As for the if(const x = 5) being like if(x = 5) being confused with if(x==5), if(const x == 5) would throw an error anyway.

# Isiah Meadows (7 years ago)

I disagree, I feel it's too clever. It'd make sense in a control flow statement where in the alternate branch, the value is meaningless/useless/ineffable/etc. (and this is why for loops commonly allow such syntax already), but in ordinary function calls, it seems like it's just trying to golf the code without good reason.

It's not visually ambiguous, just pointless in my opinion (a solution in search of a problem).


Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Naveen Chawla (7 years ago)

I understand the fear about "bad code" potentially sprouting from it. I guess I'm not as bothered as long as I can do what I want. So it's a matter of how heavily weighted the "potential bad practice" concern is over developer power.

The advantage is that it's as powerful as the developer wants it to be, and makes the common cases in if and while easy to learn and do.

For example, you can do while( x > (const y = getYFromX(x) ) ), which none

of the other ideas directly allow.

Which brings me to the next point. How would otherwise do this in a while loop? I presume the while(;) based initialization part would only operate at the start of the loop, to be consistent with for loops. So how would you make a scoped variable initialization on every iteration?

# Isiah Meadows (7 years ago)
  1. My concern with while is that it's a little too duplicative of C-style for functionally (although it's not an exact partial clone). That's why I backtracked on it right as I proposed it (within the same email).
  2. The scope would have been just like if, where it's scoped to the body with an implicit inner block scope. Anything different would be surprising and unintuitive.

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Naveen Chawla (7 years ago)

Obviously scoped, agreed, but again how would you allow scoped initialization upon each iteration, or is it your preference not to allow that? (again, initializers-as-expressions allows that, despite the other concerns).

# Isiah Meadows (7 years ago)

Per-iteration scoping would work just as it does with for (const foo of bar) { ... } now, and if it were to make it in (I'm mildly against the feature, BTW), I'd prefer it to be per-iteration like that.

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Isiah Meadows (7 years ago)

As a counterpoint, Rust and Swift are the opposite: it's only defined in the consequent branch, not the alternate. So it could go both ways.

But if a value is useful in both branches, I'd prefer to just define the variable in a separate statement, to clarify that it's useful in both branches (explicit > implicit). To take your example, I'd prefer

instead to do this:

let foo = getFoo()

if (foo.isReady()) {
    foo.start()
} else {
    foo.wait()
}

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Naveen Chawla (7 years ago)

I don't get it. Please give an example of the per-iteration initialization in the while loop... (the for-loop version before ; does it before iteration, not per-iteration)

# Isiah Meadows (7 years ago)

Maybe this analogue to the for (const ... of ...):

function keyList(map) {
    const list = new Array(map.size)
    const iter = map.keys()

    while (const {done, value} = iter.next(); !done) {
        list.push(value)
    }

    return list
}

But in general, the more I've thought about it, the more I've noticed it doesn't generalize well past the C-style for loop and I find myself getting ready to reinvent an awkward minor variant of it repeatedly. And without anything to the tune of pattern matching (which Rust has) or a loop/recur-like while-ish loop (which is what Clojure uses instead), it just seems pointless.


Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Naveen Chawla (7 years ago)

OK, but your example wouldn't be acceptable in JavaScript, because it's inconsistent with how for(;;) does initialization before the first ;, which is before iteration.

That's why I was saying that initializers-as-expressions simplifies doing things like that, despite the other concerns.

# Waldemar Horwat (7 years ago)

On 03/22/2018 11:21 PM, Naveen Chawla wrote:

I'm still not seeing a compelling case for not allowing const / let declarations to be evaluated as expressions. Or I've missed it.

Yes, I think there is a compelling case for not allowing const / let declarations to be evaluated as expressions.

As was noted,

if(x = 5) is already allowed.

Is if(const x = 5) really that much of a stretch?

That's fine in this case, and I happily use this construct in C++.

But that's very different from allowing const / let declarations anyplace you allow an expression.

To answer a concern about a function call like myFunction(const x = 7), of course the scope of x would be where it is declared.

And here we come the problem: the scope.

It can't be anywhere else (like inside myFunction or something).

We wouldn't want to repeat the var hoisting problems, so the scope of a general subexpression declaration would need to be the subexpression in which it's contained and not some outer context. If you don't do that, you'll eventually run into the same problems as with var.

However, that's not what the current uses of such declarations do. For example,

for (var i = expr; ...) {...}

scopes i to the body of the loop, and you get a new i binding for each iteration, which is important for lambda capture, even though expr is evaluated only once. Subexpression scoping would be incompatible with that.

As such, we can reasonably allow const / let declarations in other specific contexts such as at the top level of if statement condition expressions, but not in subexpressions in general.

 Waldemar
# Michael Theriot (7 years ago)

Same sentiments, and I am pleased with how golang handles this common desire. Another idea I had is a for statement with only one expression of declarations, or even a new use for the dead with statement (conveniently named).