A couple of questions regarding let, hoisting and block scope

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

A couple of questions regarding let, hoisting and block scope

Claus Reinke
I was looking forward to a Javascript with block scope at last,
but on looking through the proposals, I have some questions:

1. hoisting vs recursive function definitions

    Hoisting isn't nice in general, and from the "no use before
    declaration" in [1], it seems that let bindings won't be hoisted,
    not even to their enclosing block.

    But hoisting is also the basis for making mutually recursive
    function definitions work without pain. Will we have to
    declare all function names of recursive function groups
    ahead of defining them (with a top-down parser, there'd
    be many more than just two function names to list)?

    {
    let odd, even; // needed?
    odd = function (n) { .. even(n-1) ..}
    even = function (n) { .. odd(n-1) ..}
    }

    or, with #functions [2]

    {
    const odd, even; // needed?
    const #odd (n) { .. even(n-1) ..}
    const #even (n) { .. odd(n-1) ..}
    }

    Once function definitions are constant, there doesn't seem
    to be much harm in a limited form of hoisting: for a sequence
    of constant function definitions, not interrupted by other
    statements, implicitly introduce all function names defined
    in the sequence at the start of the sequence (to simplify
    recursive definitions).

    The alternatives would be manual duplication of function
    name lists, or introducing a dedicated letrec syntax for
    recursive definitions (the latter might actually be preferable).

    Am I missing something here, or hasn't this been discussed?
   
2. ease of transition

    The general idea seems to be to introduce separate syntax,
    to force programmers to "buy in" to the new semantics. This
    should lead to a clean transition, but not an easy one.

    The downside is that no-one can test the waters as long as
    old implementations (do not understand 'let') retain substantial
    marketshare. This is sad because implementations could
    start helping programmers right now (read: from the next
    release), to prepare for the eventual transition.

    One idea would be to start separating "strong" and "weak"
    blocks, where weak blocks '{ }' are the standard, non-scoped
    ones and strong blocks '{{ }}' (to steal no syntax) would be
    block-scoped (for instance, map to "(function() { }())" ).

    [we can't map '{{ }}' by translating 'var' to 'let': unless all
     blocks involved are strong blocks, 'let' is more local]

    Another idea would be to add a pragma: "no hoisting";
    (or extend "use strict" to encompass this). Upon which
    the implementation should warn or error on any code
    that captures variable occurences by hoisting. For instance:

    function F() {
        "no hoisting";
        .. x ..
        if ( .. ) { var x; .. }
        .. x ..
    }

    should produce warnings (at least at the hoisted declaration,
    probably also at the captured uses).    

Claus

[1] http://wiki.ecmascript.org/doku.php?id=harmony:let
[2] http://brendaneich.com/2011/01/harmony-of-my-dreams/
 
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A couple of questions regarding let, hoisting and block scope

Dmitry Soshnikov
On 21.03.2011 19:13, Claus Reinke wrote:
> I was looking forward to a Javascript with block scope at last,
> but on looking through the proposals, I have some questions:
>
> 1. hoisting vs recursive function definitions
>
>    Hoisting isn't nice in general,

It's just a technique. It has pros and cons (http://bit.ly/eg4Daz)

> and from the "no use before    declaration" in [1], it seems that let
> bindings won't be hoisted,
>    not even to their enclosing block.
>

Why? It does hoisted (at least in the semantics of current SM1.8.5). In
rough approximation, `let` is just a syntactic sugar for immediately
invoked lambdas:

let (x = 10, y = 20) {
   /* code */
}

is just a:

(function (x, y) {
     /* code */
})(10, 20);

A similarly:

if (true) {
     console.log(x, y); // undefined, undefined
     let x = 10, y = 20;
     console.log(x, y); // 10, 20
}

is just a:

if (true) {
     (function () {
         console.log(x, y); // undefined, undefined
         var x = 10, y = 20;
         console.log(x, y); // 10, 20
     })();
}

That said, the approximation is rough (after all, things such as
`break`, `continue`, etc. should be considered), but the basic idea is this.


>    But hoisting is also the basis for making mutually recursive
>    function definitions work without pain. Will we have to    declare
> all function names of recursive function groups    ahead of defining
> them (with a top-down parser, there'd
>    be many more than just two function names to list)?
>    {    let odd, even; // needed?
>    odd = function (n) { .. even(n-1) ..}
>    even = function (n) { .. odd(n-1) ..}
>    }
>

Nope, it seems ugly. If you want a function expression (FE), then use it
as is:

let odd = #(n) {
   /* code */
};

Or, since function statements (FS) will be standardized, just:

function odd(n) {
   /* code */
}

>    or, with #functions [2]
>
>    {    const odd, even; // needed?
>    const #odd (n) { .. even(n-1) ..}
>    const #even (n) { .. odd(n-1) ..}
>    }
>
>    Once function definitions are constant, there doesn't seem
>    to be much harm in a limited form of hoisting: for a sequence
>    of constant function definitions, not interrupted by other    
> statements, implicitly introduce all function names defined    in the
> sequence at the start of the sequence (to simplify    recursive
> definitions).
>

Yes, definitions in loop is also one of the hoisting's reasons (though,
it can be managed an in the system without hoisting).

>    The alternatives would be manual duplication of function    name
> lists, or introducing a dedicated letrec syntax for    recursive
> definitions (the latter might actually be preferable).
>
>    Am I missing something here, or hasn't this been discussed?
>    2. ease of transition
>
>    The general idea seems to be to introduce separate syntax,
>    to force programmers to "buy in" to the new semantics. This
>    should lead to a clean transition, but not an easy one.
>
>    The downside is that no-one can test the waters as long as    old
> implementations (do not understand 'let') retain substantial    
> marketshare. This is sad because implementations could
>    start helping programmers right now (read: from the next
>    release), to prepare for the eventual transition.
>
>    One idea would be to start separating "strong" and "weak"
>    blocks, where weak blocks '{ }' are the standard, non-scoped
>    ones and strong blocks '{{ }}' (to steal no syntax) would be
>    block-scoped (for instance, map to "(function() { }())" ).
>

Not sure and don't think so. Also, it will complicate the picture in a
whole. Two curly braces are enough for JS from C's syntax.

foo = ->
     # coffee

let foo = #() {{
    // js
}}

>    [we can't map '{{ }}' by translating 'var' to 'let': unless all
>     blocks involved are strong blocks, 'let' is more local]
>
>    Another idea would be to add a pragma: "no hoisting";
>    (or extend "use strict" to encompass this). Upon which
>    the implementation should warn or error on any code
>    that captures variable occurences by hoisting. For instance:
>
>    function F() {
>        "no hoisting";
>        .. x ..
>        if ( .. ) { var x; .. }
>        .. x ..
>    }
>

Also it seems as a complication for the language. Again, hoisting is
just a technique, there is no need to complicate your code with
additional pragmas (especially with that technically-jargon term
"hoisting"). It's better to build your code accordingly and manually --
just put all definitions to the top yourself.

Dmitry.

>    should produce warnings (at least at the hoisted declaration,
>    probably also at the captured uses).
> Claus
>
> [1] http://wiki.ecmascript.org/doku.php?id=harmony:let
> [2] http://brendaneich.com/2011/01/harmony-of-my-dreams/
>
> _______________________________________________
> es-discuss mailing list
> [hidden email]
> https://mail.mozilla.org/listinfo/es-discuss

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A couple of questions regarding let, hoisting and block scope

Dave Herman
In reply to this post by Claus Reinke
>   Hoisting isn't nice in general, and from the "no use before    declaration" in [1], it seems that let bindings won't be hoisted,
>   not even to their enclosing block.

That page is not yet complete. There's plenty more work to do on it, but we probably won't be able to find much time to do that work till after May, I'm afraid.

>   But hoisting is also the basis for making mutually recursive
>   function definitions work without pain. Will we have to    declare all function names of recursive function groups    ahead of defining them (with a top-down parser, there'd
>   be many more than just two function names to list)?

Function declarations (whether via |function foo(...) { ... }| or |const #foo(...) { ... }|) will almost certainly be hoisted.

>   {    let odd, even; // needed?
>   odd = function (n) { .. even(n-1) ..}
>   even = function (n) { .. odd(n-1) ..}
>   }

Pre-declaring odd and even would be needed for this form, yes. (If someone wants to change JS to have Python/CoffeeScript-like implicit variable scope, they'll have to get past my dead body first.) But for function definitions, you're better off using function declarations rather than assignment.

>   or, with #functions [2]
>
>   {    const odd, even; // needed?
>   const #odd (n) { .. even(n-1) ..}
>   const #even (n) { .. odd(n-1) ..}
>   }

As I say, since these are the declarative form, you get hoisting and don't need to pre-declare them.

Working out the details of the scoping semantics is really subtle, though. When you have hoisting of functions, the spec has to cope with situations like:

    {
        let x;
        {
            foo();
            let x;
            function foo() { ... x ... }
        }
    }

There are a bunch of careful details we have to work through, but we haven't gotten to it yet.

This really isn't a good time or venue to discuss the details. The spec isn't complete or ready for review, and es-discuss is not the place to do that work.

>   One idea would be to start separating "strong" and "weak"
>   blocks, where weak blocks '{ }' are the standard, non-scoped
>   ones and strong blocks '{{ }}' (to steal no syntax) would be
>   block-scoped (for instance, map to "(function() { }())" ).

Ugh. Just... no.

Dave

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss