Ticket #240 (reopened defect)

Opened 1 year ago

Last modified 1 year ago

Do let blocks pay for themselves?

Reported by: jeffdyer Assigned to: anonymous
Type: defect Priority: major
Milestone: Component: Proposals
Version: 4 Keywords:
Cc: waldemar@google.com, pascallouis@google.com, lth, brendan, jeffdyer, chrispi, ibukanov

Description

Let's decide once and for all

Attachments

Change History

Changed 1 year ago by jeffdyer

  • cc changed from waldemar, lars, brendan, jeffdyer to waldemar@google.com, lth, brendan, jeffdyer

Changed 1 year ago by brendan

  • summary changed from Do let statements pay for themselves? to Do let blocks pay for themselves?

While being past the proposal freeze does not mean we can't cut anything, this is a non-trivial change. The presence of let blocks is not just for completeness or symmetry in the face of JS's C heritage of statement and expression sub-grammars. They are not emulatable in full with let expressions or let declarations, since you sometimes want to rename outer names while initializing the inner ones using (a mix or permutation of) the outer names' values.

I realize that page calls 'em "let statements", but I'm gonna advocate "let block" to avoid confusion (my own, if no one else's).

/be

Changed 1 year ago by brendan

"that page" in my comment is of course

http://wiki.ecmascript.org/doku.php?id=proposals:block_expressions

/be

Changed 1 year ago by jeffdyer

Agreed, that being past proposal freeze should mean something. It's easy to forget how well picked the tree is. What appear to be low hanging probably isn't fruit at all.

Pasting your email comment, for the record:

> We have 3 forms of let:
>
>    let (x=y) x
>    let x = y
>    let (x=y) { print (x) }
>
> We have ample evidence that the first two forms are useful. The
> third does
> not as clearly pay for itself, but has survived because of symmetry
> with
> function statements (definitions really) and their braceless
> expression
> forms. I think the analogy is artificial and I, at least, could do
> without
> let statements.
(let block is the preferred term, I think.)
As my slides from past XTech talks on JS2 point out, people today use:
(function () {
     var x, y, z;
     ...
})()
as poor-man's alternatives to let expressions and let statements,
according as the ... ends in a return or not. The let block form is
used in the expansion of for-in loops, in the iterators and
generators proposal and the white paper:
let ($it = o.iterator::get(true)) {
     while (true) {
         try {
             i = $it.next()
         }
         catch (e: iterator::StopIterationClass) {
             break;
         }
         print(i)
     }
}
It can't be emulated with a let expression. It could be emulated with
let declarations in a block, but at a price: no way to rebind outer
names that are initialized from the outer bindings' values, due to
hoisting.
This is the reason we argued for both let blocks and let expressions.
They allow one to rebind names that would be shadowed:
let (x = y, y = x) {
     . . .
}
which can't be done at all with let declarations, and if ... doesn't
fit in an expression (e.g., contains a loop), can't be done with let
expressions.
So order your list like so and note the separate rationales:
    let x = y                      // repeat after me: "let is the
new var"
    let (x=y) x                    // for temporaries in expressions
(macro-ready!)
    let (x=x) { mutate x freely }  // inner binding initialized from
shadowed outer
All three are implemented in JS1.7 in Firefox 2. I would view with
alarm removal of any of these.
/be

Changed 1 year ago by brendan

Something I wrote in

http://wiki.ecmascript.org/doku.php?id=discussion:block_expressions

that might be persuasive here:

Again, it seems to me that convenience can trump minimalism – not always, but in several cases near the surface of the language. Minimizing the language so that there is only one way to say something goes against the grain of scripting languages (Perl overdoes this, but its heart is in the right place). Over-minimizing the surface may optimize language size at the expense of real-world usability.

Python goes the other way, and strives for one way to say things, but it too allows some overlap, since usable languages are like that (lwall the linguist makes a pretty good case for why in his Perl writings, even if as I say Perl goes to far).

This is a generality, so perhaps not as helpful as it should be for me to repeat it here. OTOH some folks may not have read the discussion (or proposal) page; some may want to re-read to avoid repeating arguments.

The "no way to emulate" point is questionable if the use-case is considered so rare that we can blow it off. That let expressions support rebinding outers to values that use the outer names could just be an aspect of let expressions that will also be rarely or never used. I'm looking for Firefox core JS, extension JS, and XUL app JS use-case examples now. I'll report back. I'm still in favor of let-blocks on first principles, including the general minimalism-is-not-primary one above.

/be

Changed 1 year ago by brendan

Here's an example:

http://lxr.mozilla.org/mozilla/source/toolkit/mozapps/downloads/content/downloads.js

Apart from being able to use outer names in initializers in the let head, there is some intrinsic benefit to let blocks over blocks containing let declarations. "let is the new var" could be used instead, but the let head has the nice effect of putting the declarations first, and avoiding hoisting, with its odd "which name did I reference?" and "used before set? undefined or default value" issues.

IOW "let is the new var" is good for the masses, but "let blocks are even better" seems to be sticking for some elites.

More real-world data when I have it.

/be

Changed 1 year ago by lth

I think they're kinda ugly, but I use them, and where I use them I think they make the code clearer than it would have been without:

$ grep 'let *(.*) *{' *.es
GenericFunction.es:            let (next = any(acc, candidateDirectSuperclass)){
GenericFunction.es:        let (dsc = directSuperClasses(t)) {
RegExpCompiler.es:            let (a : Matcher? = assertion()) {
RegExpCompiler.es:            let (t : CharsetMatcher? = characterClassEscape()) {
RegExpCompiler.es:            let (t : string? = characterEscape(false)) {
RegExpCompiler.es:            let (t : double? = decimalEscape()) {
RegExpCompiler.es:            let (t : CharsetMatcher? = characterClassEscape()) {
RegExpCompiler.es:            let (t : string? = characterEscape(true)) {
RegExpCompiler.es:            let (t : double? = decimalEscape()) {
RegExpCompiler.es:                let (c : string = peekChar()) {
RegExpCompiler.es:            let (c = consumeChar()) {
RegExpCompiler.es:                let (c = peekChar()) {

Anyhow, they're virtually free. My vote is in favor of keeping them.

Changed 1 year ago by brendan

Don't call your baby ugly :-P.

Apart from looks, which I do not mind, they're akin to let/in/end in ML, refracted through the statement vs. expression prism. Can we keep 'em? Anyone still object?

/be

Changed 1 year ago by chrispi

I've seen this construct used a few times as well:

(function () {

var x, y, z; ...

})()

And I would certainly use them (as I do in ML).

Changed 1 year ago by chrispi

  • cc changed from waldemar@google.com, lth, brendan, jeffdyer to waldemar@google.com, lth, brendan, jeffdyer, chrispi

Changed 1 year ago by waldemar

One thing that bothers me about let-blocks is that one has to remember when to use parentheses and when not to. Getting it wrong will result in subtle, silent changes to code behavior. Contrast:

let (c:string = peekChar())
expr(c)
...

with:

let c:string = peekChar()
expr(c)
...

Changed 1 year ago by waldemar

  • cc changed from waldemar@google.com, lth, brendan, jeffdyer, chrispi to waldemar@google.com, pascallouis@google.com, lth, brendan, jeffdyer, chrispi

Changed 1 year ago by brendan

That parenthesis dependency bites both let blocks and let expressions. This bug is not about to do away with let expressions, which have fewer doubters (no trac ticket asking about them). Happy to discuss both here -- I just wanted to note the breadth of the counter-argument.

Having shipped all of these in Firefox 2, we've never heard of any such mistakes. They might be hard to catch. JS in the field has too many unintended global variables bound by assignment (missing var). But the let forms in Firefox 2 are used mainly in XUL JS, a corpus that's easier to check for errors. I'll try to find some.

If Google folks could search their cache of the web for let bindings in Web JS, and show some rough, non-zero statistics, that would rock hard.

/be

Changed 1 year ago by brendan

An advantage of the parentheses is that you might drop the opening paren by mistake, somehow, but it's unlikely that you'll lose the closing paren too. If you just don't know to parenthesize, then you're using let as the new var (and life is still better).

The greater ugliness, more in terms of implementation that usability, based on our experience in Firefox 2, is the fact that let declarations hoist to top of block (if not in a for loop head). But we reckoned the design is simpler if let and var both hoist. The curse of hoisting continues to haunt us!

/be

Changed 1 year ago by lth

I do not think there is room for much confusion wrt let-is-the-new-var vs let expressions. The latter usually occur inside another expression:

timeval = let (t = LocalTime(timeval))
              UTCTime(MakeDate(Day(t), MakeTime(HourFromTime(t),
                                                MinFromTime(t),
                                                sec,
                                                ms)));

I've written a fair amount of ES4 code now and the only time I've had occasion to let expressions as the outermost expression is in expression functions, as in the Date class:

intrinsic function getMonth() : double
    let (t = timeval)
        isNaN(t) ? t : MonthFromTime(LocalTime(t));

With experience, I've come to reduce this use to a minimum as well, because the expressions tend to become too complicated (ES is not Lisp, for better or worse).

Changed 1 year ago by lth

  • status changed from new to closed
  • resolution set to fixed

Resolved by some of the agreements on #253: let blocks do pay for themselves.

Changed 1 year ago by waldemar

  • status changed from closed to reopened
  • resolution deleted

I don't see anything in #253 that leads to the conclusion that let blocks pay for themselves.

Changed 1 year ago by lth

  • status changed from reopened to closed
  • resolution set to fixed

The discussion in #253 made it clear that there is agreement among core members of the TG that the scope of a let-defined name is the entire block in which it is defined. Ergo, binding forms of the type

   let (x = x) { ... }

or even

   let (x = y) { let y = ... }

provide functionality that would otherwise be available by clumsier means (renaming, use of other variable names). Discussion earlier in the present ticket indicates that members of the TG believe that functionality to be useful. The complexity cost of the functionality is in any case minimal. Finally, the functionality has been accepted by the TG at an earlier date. Taken together, I see this as clear support in favor of the functionality, and there is no reason to assume that it will be removed from the language. Therefore, the issue is closed.

Changed 1 year ago by waldemar

  • status changed from closed to reopened
  • resolution deleted

I'm not convinced that the grammar for let-statements is self-consistent, and they do lead to confusion. Where can you use these things? Are they statements or expressions? Are let-expressions statements or expressions?

Changed 1 year ago by lth

Let blocks are normal statements and can be used everywhere statements can be used.

Let blocks are syntactically unambiguous, though they are separated from let expressions only when you see the opening '{'.

Let expressions are expressions and can occur (unrestrictedly) in expression statements.

Hopefully Jeff will speak to the issue of syntactic coherence here. IMO there are no new problems here.

Changed 1 year ago by brendan

Please point out a specific grammatical inconsistency. Since we shipped support for let declarations, blocks, and expressions in Firefox 2, JS1.7, I don't believe there is a syntactic problem. Nor do let blocks seem to lead to confusion, in the field. See for example:

http://lxr.mozilla.org/mozilla/source/toolkit/mozapps/downloads/content/downloads.js#446

I do not think this bug should be reopened without demonstration of a specific problem in the grammar, or a proof that let blocks are equivalent to some other form already in a proposal. I'll leave it open for now, and it can be put on an agenda for a TG1 meeting if necessary. I'm hopeful, though, that we can agree to close it in the absence of actual syntactic or usability problems.

/be

Changed 1 year ago by ibukanov

  • cc changed from waldemar@google.com, pascallouis@google.com, lth, brendan, jeffdyer, chrispi to =waldemar@google.com, pascallouis@google.com, lth, brendan, jeffdyer, chrispi, igor@mir2.org

Changed 1 year ago by ibukanov

  • cc changed from =waldemar@google.com, pascallouis@google.com, lth, brendan, jeffdyer, chrispi, igor@mir2.org to =waldemar@google.com, pascallouis@google.com, lth, brendan, jeffdyer, chrispi, ibukanov

Changed 1 year ago by pascallouis

  • cc changed from =waldemar@google.com, pascallouis@google.com, lth, brendan, jeffdyer, chrispi, ibukanov to waldemar@google.com, pascallouis@google.com, lth, brendan, jeffdyer, chrispi, ibukanov
Note: See TracTickets for help on using tickets.