Skip to content
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

About expressions in the header of a block #28

Open
sffc opened this issue Jul 24, 2018 · 10 comments
Open

About expressions in the header of a block #28

sffc opened this issue Jul 24, 2018 · 10 comments

Comments

@sffc
Copy link

sffc commented Jul 24, 2018

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.

@bakkot
Copy link
Collaborator

bakkot commented Jul 24, 2018

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.

@dherman
Copy link
Member

dherman commented Jul 25, 2018

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
Copy link
Member

dherman commented Jul 25, 2018

@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.

@sffc
Copy link
Author

sffc commented Jul 25, 2018

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).

@dherman
Copy link
Member

dherman commented Jul 25, 2018

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.

@bakkot
Copy link
Collaborator

bakkot commented Jul 25, 2018

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.

@dherman
Copy link
Member

dherman commented Jul 25, 2018

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
}

@bakkot
Copy link
Collaborator

bakkot commented Jul 25, 2018

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!
}

)

@dherman
Copy link
Member

dherman commented Jul 25, 2018

I think we're on the same page.

Ah, cool! 👍

@dherman
Copy link
Member

dherman commented Jul 25, 2018

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

Good catch, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants