proposal-do-expressions icon indicating copy to clipboard operation
proposal-do-expressions copied to clipboard

About expressions in the header of a block

Open sffc opened this issue 7 years ago • 10 comments

Follow-up from today's TC39 discussion:

It seems to me that if you have the code,

function(x = do { 100 }) {
  // ...
}

then that should be semantically the same as,

function(x) {
  if (typeof x === "undefined") {
    x = do { 100 };
  }
  // ...
}

return, break, var, etc. all just become part of the function body.

This makes it clear what the default expression turns into, and avoids the edge cases that you presented today and that will likely continue to pop up as long as new features are added to the language.

sffc avatar Jul 24 '18 23:07 sffc

return, break, var, etc. all just become part of the function body.

var can't while being consistent with the rest of the language, in particular with function(x = eval('var y') {}.

I am in favor of banning var in all do expressions everywhere, though, which avoids the problem.

bakkot avatar Jul 24 '18 23:07 bakkot

Hi @sffc!

then that should be semantically the same as

I don't agree with this -- because these questions are about scope, the syntactic placement of the source text matters. If you define the semantics according to a rewrite that moves the placement of a declaration, you can change the scope in ways that don't line up with the intuitions conveyed by the original syntax.

dherman avatar Jul 25 '18 18:07 dherman

@bakkot I think the sweet spot here is to make function parameter expressions a scope where var is disallowed (because its meaning is ambiguous, as we discussed), but to keep this orthogonal from do itself—for a couple reasons. Philosophically, the goal is for do to be as transparent a grouping syntax for expressions as block statements are for statements. Instead of adding special case restrictions to do itself, we can simply clarify the scoping questions that it exposes. Practically, defining the static semantics at the level of scoping instead of feature combinations allows us to have other expression forms that have statement bodies, like the pattern matching or using proposals, without building a growing matrix of disallowed features.

dherman avatar Jul 25 '18 19:07 dherman

An issue I see with saying "keyword X is not allowed in default expressions" is that we are essentially creating a restricted subset of JavaScript for use in this one particular construction; for perpetuity, we will need to consider whether any new language keywords should be included in this peculiar subset allowed in default expressions.

Assigning the default expressions to be first-class members of the inner scope seems like a clean solution.

the syntactic placement of the source text matters

The expressions in the header of a function are written in the same place in the code as the function definition. So to me, I don't see how this would be "moving" the placement of the code.

If you define the semantics according to a rewrite that moves the placement of a declaration, you can change the scope in ways that don't line up with the intuitions conveyed by the original syntax.

The var keyword is function-scoped. If I were to see the var keyword in a default expression, there are two choices for which scope it belongs to (outer or inner), and I think it is reasonable intuition to say that it belongs to the scope of the function being declared (inner).

sffc avatar Jul 25 '18 19:07 sffc

The expressions in the header of a function are written in the same place in the code as the function definition. So to me, I don't see how this would be "moving" the placement of the code.

They moved from the parameter list to the body. And JavaScript already does distinguish those. For example:

function f(thunk = () => window) {
    var window = "inner variable";
    return thunk;
}
let thunk = f();
thunk() === window // true!

If I were to see the var keyword in a default expression, there are two choices for which scope it belongs to (outer or inner), and I think it is reasonable intuition to say that it belongs to the scope of the function being declared (inner).

I agree, except there's also a third choice, which is to say that this isn't a scope where vars can be declared. Given that parameter lists are not considered in scope of the var declarations that appear in the body, I find this to be a pretty consistent (and conservative) choice.

dherman avatar Jul 25 '18 19:07 dherman

Practically, defining the static semantics at the level of scoping instead of feature combinations allows us to have other expression forms that have statement bodies, like the pattern matching or using proposals, without building a growing matrix of disallowed features.

That seems fine to me, although if I'm understanding correctly that's mostly a point about the implementation details of how it would be banned and establishing an expectation for future proposals, rather than an observable difference in semantics, right? That is, the practical effect (in the absence of other future proposals) would still be "you can't use var in parameters in do-expressions, and you can in all other do-expressions".

I still personally prefer banning var in do-expressions, but whatever.

@sffc

The var keyword is function-scoped. If I were to see the var keyword in a default expression, there are two choices for which scope it belongs to (outer or inner), and I think it is reasonable intuition to say that it belongs to the scope of the function being declared (inner).

It's already possible to see the var keyword in a parameter expression, and it belongs neither to the outer nor the inner scope: (function(a = 0, b = eval('var a = 1; console.log(a);')) { console.log(a); })() prints 1, 0.

There's an open PR changing this behavior, but it's still not going to put vars in parameter expressions into the body of the function. Dave's example illustrates why that would be bad, I think.

bakkot avatar Jul 25 '18 19:07 bakkot

That is, the practical effect (in the absence of other future proposals) would still be "you can't use var in parameters in do-expressions, and you can in all other do-expressions".

No, it's only disallowed in the context of parameter default expressions. In other contexts it's allowed, for example:

function f() {
    let x = do {
        var y = 41;
        1
    };
    console.log(x + y) // 42
}

dherman avatar Jul 25 '18 19:07 dherman

No, it's only disallowed in the context of parameter default expressions.

That's what I meant, yeah: "you can't use var in parameters in do-expressions" (I guess I got those in the the wrong order, and should be "you can't use var in do-expressions in parameters"). I think we're on the same page.

(Incidentally, it's not just parameter defaults which can introduce expressions:

function f({ [do { var x; 'a'}]: a }) {
  // look ma, no defaults!
}

)

bakkot avatar Jul 25 '18 20:07 bakkot

I think we're on the same page.

Ah, cool! 👍

dherman avatar Jul 25 '18 20:07 dherman

Incidentally, it's not just parameter defaults which can introduce expressions

Good catch, thank you!

dherman avatar Jul 25 '18 20:07 dherman