function hoisting like var

Ingvar von Schoultz ingvar-v-s at
Wed Jul 30 15:13:34 PDT 2008

Regarding my explanations quoted below, did they clarify

It would be a relief to know that things are now clear and
understandable, if they are. If there are still things
that seem unclear, strange, incompatible or impractical,
please ask about them!

After some thinking I've found that I could have explained
some of the things below better, making them clearer. Should
I post better explanations?

I'm really very curious about that rebinding problem! Both
what it means, and where I seem to propose it (or actually
propose it, inadvertently).


Ingvar von Schoultz wrote:
> Sorry about the length of this, but I'm trying to cover the
> unclear things, and often I don't know which things are unclear.
> Brendan Eich wrote:
>> On Jul 26, 2008, at 2:07 PM, Ingvar von Schoultz wrote:
>>> You can't get away from supporting this:
>>>       {
>>>           function a(){}
>>>           var b = a;
>>>       }
>>> ES4 is planning to support function declarations locally
>>> bound in blocks, so the above is valid ES4 code.
>>> What you see above is function b() hoisting like var.
>>> (I said b, not a.)
>> What you said does not make sense.  It's true that var b is hoisted to
>> the top of the program or function body. But it is not initialized  
>> until control flows through the assignment b = a that is part of the  
>> var declaration. So there is no capture problem.
> That's my point! There isn't any capture problem. That's exactly
> what I'm showing here. And, more importantly, you can't insert a
> capture problem while keeping the structure intact. The arrangement
> is inherently well-behaved.
> I'm trying to show that you can support functions that hoist like
> var in a well-behaving way, and that this is /not/ complicated.
> And the snippet is intended as proof.
> But perhaps I should interpret what you said somewhat differently.
> The snippet, as shown here, does not have a capture problem.
> But if you "improve" the hoisting carelessly, there's a threat
> of a capture problem lurking within it. (That's assuming that
> I understand "capture problem" correctly -- this is a rare
> case of Wikipedia not explaining a computing term.)
> Somebody might want to "improve" the hoisting by making it so
> that the function is assigned (is callable) from before we enter
> the global scope.
> Let's assume, for the argument, that the above code magically
> starts to behave that way. Then it will work fine as long as it
> stays the same. But later the programmer might decide to add a
> local variable:
>      {
>          function a(){}
>          var b = a;
>          let c = 3;
>      }
> c is now visible from inside a(), but exists only when we enter
> the block. So in this situation a() must not be called before
> we enter the block. The function must no longer be assigned
> (callable) at the top of the global scope.
> I would consider it extremely surprising semantics if I can call
> a() above the block, but only before I add that local variable,
> and this suddenly changes just because I add c. It's a huge change,
> it's far away, and it's unrelated.
> So for the sake of consistency and predictability we must assign
> |undefined| whenever the code structure allows a capture problem
> to be inserted as a side effect of doing something unrelated,
> like adding |let c|.
> That's why I said this, in the email where I first showed this
> snippet:
> ,-------
> |  Assigning |undefined| is correct for any function whose
> |  assignment depends on sequential code. The above is such a
> |  sequential dependency, even though it may not look that way.
> `-------
> It may not look sequential, but that's just because I left out
> a lot of details, in an attempt to keep it as brief as ever
> possible, to minimize any misunderstandings.
> As you can see, in my opinion, what I'm saying does make perfect
> sense!
> The claim that just because of this limitation my proposal
> "doesn't work" is in my opinion quite mistaken. I consider
> this functionality fully acceptable. It's simple, understandable
> and predictable. Sure it's a limitation, but a minor one. It's
> /far/ better and more useful and intuitive than having functions
> not hoisting out at all.
> The usual use case will be an if() or some such. Then the
> programmer fully expects the function to be available only
> afterward. Very useful. Using it in a bare block like the
> above will be unusual, but if used, the rules are simple.
>>> There is no far-too-complicated split-scope complexity. There
>>> is no capturing of variables that haven't been declared yet.
>>> It's simple, intuitive, well-defined and well-behaved.
>> Thanks, I agree. But it is not what you proposed.
> Sorry, I don't understand. What is not what I proposed, and
> in which one of my proposals did I not propose it?
>> Again, from  
>> Waldemar's original reply, but with your proposed {{}} interpolated  
>> and the elided code amended to say what the consequence is:
>> // outer scope
>> function c() ...;
>> // inner scope
>> {{
>>    if (foo) {
>>      const c = 37;
>>    }
>>    ... c in your proposal must be hoisted to the {{,
>>        so it can't be function c -- yet it can't be
>>        initialized to 37 if foo is "falsy" ...
>> }}
> Yes, c exists in the inner scope {{ }}. It exists there from
> before you enter the scope and throughout. It shadows the outer
> c() throughout. If foo is false, the constant c is never initialized.
>> You could reply that const is new (sort of -- two browsers already  
>> implement it one way, another treats it as var) and therefore should  
>> always scope to { or {{, whichever is closer. But the point stands if  
>> you replace const with function or var and hoist to the {{.
> Which point? Forgive me, I didn't see any point. I don't see any
> problem or disadvantage with that uninitialized c. It's what the
> programmer coded.
> It's also how |var| works. In my opinion, |var| and |const| should
> have exactly the same behavior, except for the constantness.
> It looks perfectly fine to me. So please explain.
>> Repeating  
>> the next counter-example, with {{}} changes again, to track your  
>> proposal since the original exchange with Waldemar:
>> // outer scope
>> function c() ...;
>> // inner scope
>> {{
>>    function f() {
>>      return c;
>>    }
>>    a = f();
>>    if (foo) {
>>      const c = 37;
>>    }
>>    b = f();
>>    ... just what do a and b hold here? Was f's captured
>>        variable rebound by the if statement? ...
>> }}
> c is a constant that is visible throughout the inner scope
> {{ }}, visible from before you enter the scope and throughout.
> It shadows the outer function c() throughout. It is initially
> unassigned, or assigned |undefined|.
> When you call |a = f()|, the function f() accesses this
> unassigned c. I don't know if there's a consensus about what
> should happen when you access an unassigned constant. It
> seems to me that this should raise an error. But my only
> reason for saying this is that if it doesn't, the "constant"
> becomes a one-shot binary toggle, and a toggle isn't a
> constant. So it's a detail of intuitive semantics and
> consistency.
> If instead it's specified that an unassigned constant returns
> |undefined|, then a will contain |undefined| and the program
> continues. Then |if (foo)| may or may not allow c to get its
> only-once-permitted assignment.
> I don't know why you mention rebinding. Maybe I've misunderstood
> something. But as I see it, it's just an assignment, just as if
> c were a var, except you can only assign once. You ask how f()
> sees it (I think). Well, f() sees it just like it sees any var
> in that scope. I'm not sure this answers your question, I hope
> it does.
> After that, b will receive whatever c contains, either |undefined|
> or 37.
> A different notion of constant is possible, where earlier assignment
> is enforced, or maybe even hoisted to the beginning of the scope.
> But I think the constant that I described is a /much/ better fit for
> this language.
> I hope the above description is sufficient.
> What's this talk about rebinding? Waldemar asked about rebinding,
> and unless I misunderstood, rebinding was at the basis of the
> strange things that he thought that I was proposing. I have not
> intended to propose any kind of rebinding. If there is any kind of
> rebinding inherent in any one of my proposals, I'm not aware of it.
> Maybe I'm misunderstanding something.
> I can't say anything more than that about this rebinding for now,
> but I can say this. Think about var declarations:
>      alert (a);
>      var a = 10;
>      alert (a);
> This alerts undefined and 10. If you remove |var a = 10| it will
> instead raise an error. That's because the name a is then unknown.
> So when you use |var a| anywhere within the scope, the name a
> becomes visible both before and after |var a|. |var| isn't
> something executable (in my mind model at least), it just says
> which scope the variable resides in. The result is this scoping.
> And as such, it is valid throughout the scope.
> This is a very good arrangement, and I think all declarations
> should work this way, always, consistently (all declarations
> where this makes sense).
> In the following example, one var is redundant. You can omit either
> one, the meaning remains identical:
>      function f()
>      {   var a = 10;
>          var a = 20;
>      }
> Here's another example of redundant var:
>      if (x)
>          var a = 10;
>      else
>          var a = 20;
> For consistency and simplicity, in my opinion all declarations
> should also allow this redundancy. It's an odd arrangement,
> but in fact it's useful.
> Of course such redundant declarations must be consistent. So
> the following would be a conflicting declaration error:
>      if (x)
>          var a = 10;   // ERROR -- Conflicts with const.
>      else
>          const a = 20; // ERROR -- Conflicts with var.
> I use redundant |var| everywhere in my code to signal localness.
> I do this because if I leave out |var|, this is a clear warning
> to myself that I'm accessing something external.
> I would greatly prefer having a strict mode that required me
> to use an |outer| declaration, as a much clearer warning that
> I'm accessing something external:
>      var a = 10, b = 20;
>      function f()
>      {   outer a;
>          c = outer b;
>      }
> This would be /much/ more useful than data types in very small
> programs.
>> And so on.
>>> The above is the /exact/ functionality of function hoisting
>>> like var, apart from using two names. You can refuse the
>>> clearer syntax, but you can't refuse the above code and
>>> functionality.
>> I think I see the confusion now. Do you believe that in the var b =  
>> a; code you wrote, both the binding of the var named b *and* its  
>> initialization with the value of the function object denoted a are  
>> hoisted? Hoisted up to what point?
> No, it starts out |undefined|, and that's a good thing, as detailed
> above.
> The assignment stays in place and occurs where it's written in the
> code. I couldn't express this with exactness in that code snippet.
> In my snippet's inner scope the function is assigned (is callable)
> from before you enter the inner scope, since it's written as a
> declaration rather than an assignment. Once again, this is exactly
> as should be. That snippet is carefully crafted.
> The main difference between a real hoisted function and my snippet
> is that my snippet shows the function with two different names, one
> illustrating the inner-scope assignment and the other illustrating
> the outer-scope assignment, whereas a real hoisted function would
> have only one name, bound only in the outer scope.
> I could instead have written my snippet like this instead:
>      {
>          var a = function a(){}
>      }
> Then it's only one name, hoisting out and bound in the outer
> scope, so in that regard, this is more similar to a real hoisting
> function.
> However, this does not reflect the fact that the function should
> be assigned (should be callable) from before you enter the inner
> scope. Since I wanted to refute the claim about complexity, I
> found it more relevant to show correct hoisting and assignment
> than solving the triviality of having two names.
> In fact showing it with two names might even help a little. It
> may help make clear and specific how there are two scopes involved,
> and how each one is dealt with by re-using existing functionality.
> (Or rather, I get the impression that you can re-use existing
> functionality, largely, and I've seen no specific counterargument
> so far.)
> So the one and only outer name of the hoisted function gets assigned
> the callable function just before we enter the inner scope.
>> Waldemar wrote a while back: "Keep in mind that function assignments  
>> hoist to the beginning of the scope in which the function is defined,  
>> so your proposal won't work."
>> The word "assignment" where "definition" was perhaps more precise  
>> (function definitions replace extant properties of the same name in  
>> the variable object, they are not equivalent to assignment  
>> expressions) may have misled you. From the context and the long- 
>> standing spec and implementation behavior with functions not in  
>> blocks or any other sub-statement position, it was clear (I think)  
>> what was meant, but I can see how this could be confusing.
>> Assignment expressions and initializers in var statements do not  
>> hoist or otherwise move in the flow of control.
> I hope I've shown now that I understand all this quite clearly,
> and intended exactly this behavior, and consider it necessary,
> acceptable, and very useful.
> I greatly appreciate your reply and questions. Detailed and
> specific, clarifying to me what I need to explain and answer.
> Perfect for my somewhat overly detailed mind. Thank you very
> much.
> I hope my long answer hasn't been too exhausting to read.

Ingvar von Schoultz

------- (My quirky use of capitals in code comes from my opinion that
reserved and predefined words should all start with lowercase, and
user-defined should all start with uppercase, because this will easily
and elegantly prevent a host of name-collision problems when things
like programming languages are upgraded with new labels.)

More information about the Es4-discuss mailing list