function hoisting like var -- Was: Re: Surprising semantics

2008-07-24 Thread Ingvar von Schoultz
[EMAIL PROTECTED] wrote:
 I'm trying to keep the language relatively simple. 

You can't get away from supporting this:

 {
 function a(){}
 var b = a;
 }

What you see above is function b() hoisting like var. This
is the /exact/ functionality, apart from using two names.
You can refuse the clearer syntax, but you can't refuse
the above code and functionality.

b is bound to the global scope and assigned |undefined|
before you enter the global scope.

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.

 What you're proposing
 is far too complicated.

On the contrary, the functionality already exists, as shown
above.

 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. 

When the programmer explicitly says that the assignment depends
on sequential code, then do what the programmer says. Anything
else is an error. Do it by assigning |undefined| before scope
entry. This is the only correct thing to do.

 You're
 trying to do a complex split-scope approach where each function definition
 has *two* scopes, one in which it is declared and one in which it is
 defined, but even that won't work with const, typed functions and
 variables, etc. 

Are you saying that the function /body/ gets into trouble,
or the function /name/?

The function /body/ stays where it is. The hoisting doesn't
affect it in any way. Moving the body would change its context
and meaning catastrophically. Don't touch it.

The /name/ becomes a var. Treat it like any other var. Hoist
it and assign |undefined|, exactly like you do with other vars.

Are you saying that because this var is related to a function
it can't be treated like other vars? Is this var fundamentally
different from other vars? At least above it isn't.

Or is the problem in the type declarations? Are datatypes of
vars and functions fundamentally different?

Or is it because functions have parameters? You're not saying
anything about parameters, so if that's the problem you're
being very vague indeed.

 See my previous email as to why. 

That email is about some wildly unworkable dynamic scoping.
It has nothing to do with anything I ever said. You jumped
to that conclusion.

Please stop insisting that I'm proposing that nonsense. I'm
not. I never did.

 You'd then have to
 introduce extra rules about some definitions only being possible within
 {{}} blocks, which would then affect the behavior of existing definitions
 like var if one of the other definitions within the same block was a const
 or function, which would snowball into a complex mess.

Unrelated, I believe.

Ingvar



 Ingvar von Schoultz wrote:
 I'm astonished that you interpreted my text in such a weird
 way! That's completely foreign to JavaScript!

 var is always unconditional in JavaScript. An if() before a
 declaration doesn't make the declaration conditional.

 The var takes effect long before you reach that if(). It takes
 effect before you enter the scope in which the variable resides.
 You can consider the declaration glued to the opening brace of
 that scope. Or better, glued to both braces and stretched between
 them.

 The assignment, on the other hand, stays in place and is
 conditional. But only the assignment.

 With your surprising interpretation things would be much worse
 than what your examples suggest. Much worse. Consider assigning
 to the unpredictable variable:

 function Outer()
 {
 var Test = 1;
 function Inner()
 {
 if (Unknown)
 var Test = 2; // Weird conditional declaration
 // Many
 // lines
 // of
 // code
 Test = 3;  // Semantics totally unpredictable
 }
 }

 Is the outer Test set to three, or has an inner Test been
 created that can take the value? Every program would become
 impossible to understand, an unintelligible mess.

 With my subject line surprising semantics I didn't mean to
 advocate more surprises, I wanted less!

 I assumed that ES3.1 and ES4 declarations would work like
 ES3 declarations do: The declaration spans the entire scope
 from before you enter the scope and throughout. The value
 is unassigned (undefined) until you reach an assignment.

 I took it for granted that making it constant and/or giving it
 a type would follow the same pattern. The name gets associated
 with the constantness and/or type before you enter the scope,
 and it's in effect throughout.

 In your examples with dual scopes, the result depends on whether
 you mean that the inner c is bound to the outer scope or the
 inner. If it's the outer you have conflicting, irreconcilable
 bindings that collide before you enter that outer scope, a clear
 declaration error. If it's the inner, then the inner declaration
 shadows the outer, so

{{ ... }} -- Was: Re: Surprising semantics

2008-07-22 Thread Ingvar von Schoultz
With {{ }} there are only let declarations. Var becomes let.
Hoisting to higher scopes disappears completely. The language
only supports let.

There is only one type of scope. This is the {{ }} scope.
Everything resides in a {{ }} scope, let-declared there.

When this is enabled, { } serve only to show which statements
are affected by an if(), for() etc. They have nothing at all
to do with any scoping. They don't affect any declaration in
any way whatsoever. Regarding declarations, please consider
the { } nonexistent.

For scoping, {{ }} completely replaces { }.

[EMAIL PROTECTED] wrote:
 I'm trying to keep the language relatively simple.

I agree wholeheartedly about simplicity.

  What you're proposing
 is far too complicated.

Apparently my explanations have been complicated and confusing.

 Keep in mind that function assignments hoist to the beginning of the scope
 in which the function is defined,

Yes, at the {{.

 so your proposal won't work.  You're
 trying to do a complex split-scope approach where each function definition
 has *two* scopes, one in which it is declared and one in which it is
 defined, but even that won't work with const, typed functions and
 variables, etc.

Is this about {{ }}? If so, I suppose you agree that the problem
does not appear when every declaration is a let declaration.

  See my previous email as to why.  You'd then have to
 introduce extra rules about some definitions only being possible within
 {{}} blocks, which would then affect the behavior of existing definitions
 like var if one of the other definitions within the same block was a const
 or function, which would snowball into a complex mess.

You're mixing different problems that can never appear together.

The proposal that function hoist like var is completely unrelated
to {{ }}. It doesn't apply when you use {{ }} and everything becomes
let.

I'll reply about that in a separate email. And to avoid the above
mixing and confusion, I'll put it aside for a while. Let's reduce
the confusion.

Ingvar



 Ingvar von Schoultz wrote:
 I'm astonished that you interpreted my text in such a weird
 way! That's completely foreign to JavaScript!

 var is always unconditional in JavaScript. An if() before a
 declaration doesn't make the declaration conditional.

 The var takes effect long before you reach that if(). It takes
 effect before you enter the scope in which the variable resides.
 You can consider the declaration glued to the opening brace of
 that scope. Or better, glued to both braces and stretched between
 them.

 The assignment, on the other hand, stays in place and is
 conditional. But only the assignment.

 With your surprising interpretation things would be much worse
 than what your examples suggest. Much worse. Consider assigning
 to the unpredictable variable:

 function Outer()
 {
 var Test = 1;
 function Inner()
 {
 if (Unknown)
 var Test = 2; // Weird conditional declaration
 // Many
 // lines
 // of
 // code
 Test = 3;  // Semantics totally unpredictable
 }
 }

 Is the outer Test set to three, or has an inner Test been
 created that can take the value? Every program would become
 impossible to understand, an unintelligible mess.

 With my subject line surprising semantics I didn't mean to
 advocate more surprises, I wanted less!

 I assumed that ES3.1 and ES4 declarations would work like
 ES3 declarations do: The declaration spans the entire scope
 from before you enter the scope and throughout. The value
 is unassigned (undefined) until you reach an assignment.

 I took it for granted that making it constant and/or giving it
 a type would follow the same pattern. The name gets associated
 with the constantness and/or type before you enter the scope,
 and it's in effect throughout.

 In your examples with dual scopes, the result depends on whether
 you mean that the inner c is bound to the outer scope or the
 inner. If it's the outer you have conflicting, irreconcilable
 bindings that collide before you enter that outer scope, a clear
 declaration error. If it's the inner, then the inner declaration
 shadows the outer, so the value can be unassigned or 37.

 Regarding the plethora of ways to define things, by including
 or excluding let you simply change which one of two possible
 scope opening braces the declaration is glued to for the purpose
 of name visibility. You get the same effect if you just move the
 declaration to that spot yourself, without moving the assignment.
 By including or excluding let you're simply telling the compiler
 where you want this move to go.

 Ingvar



 Waldemar Horwat wrote:
 We've been down this road before, and the arguments you present have
 been hashed out over years.  This approach doesn't work.  Read the
 archives of the ES4 group.

 The problem is that you then get a plethora of ways to define things:

 var
 const
 function
 type

Re: Surprising semantics

2008-07-21 Thread Ingvar von Schoultz
Waldemar Horwat wrote:
 The problem is that you then get a plethora of ways
 to define things:
 [...]
 Furthermore, some of them don't make sense (such as
 function without let) because they can conditionally
 capture variables that may not even exist.

Despite very much searching in the discussion archives I can't
find a single description that convinces me that there is in
fact a problem. My analyses find simple solutions for all the
situations I can think of, including nonexistent let variables.

But maybe I just haven't understood the problem. Could you give
me a link to a description?

In my analyses, all you need to do is specify what |function|
without |let| is supposed to mean, in a way that is well suited
to how ECMAScript declarations behave.

You get useful, helpful semantics, and proper throwing of errors
on incorrect access, with simple implementation, if you think how
you would manually make the function name accessible in the outer
scope, and then let the compiler make that very arrangement. The
result is useful and intuitive and avoids peculiar irregularities.

Of course it works only if it can be handled with rules that are
simple enough that the compiler can deal with all cases. I'll take
this in steps to show that the rules become simple enough.

Consider the semantics of a standard function declaration-and-
definition-combined:

 Fn();
 function Fn() {return 1}

As with any declaration in ECMAScript, the declaration of the name
Fn takes effect before you enter the scope.

This name gets the special treatment that is afforded to functions:
It is assigned its value before you enter the scope. This value is
the function object. So the call to Fn is successful.

Consider the difference when it's conditional:

 Fn();
 if (Unknown)
 function Fn() {return 1}
 else
 function Fn() {return 2}

Again, as with any declaration, the declaration of the name takes
effect before you enter the scope.

However, in this case it can't be assigned any value before you
enter the scope, since there isn't any known value. Fn exists but
is unassigned, it has the special value |undefined|. The call to
Fn() throws an error as an attempt to call undefined().

The above is equivalent to the following, which is how you would
do the same thing manually if you wanted the same result including
the bug:

 var Fn;   // Automatically assigned the value |undefined|.
 Fn();
 if (Unknown)
 Fn = function Fn() {return 1}
 else
 Fn = function Fn() {return 2}

So where the programmer wrote function declarations the compiler
arranges assignment in cases like these.

Even though this changes the behavior of function(), in that the 
assignment comes later than usual, this is not a case of hidden
surprising semantics. The programmer did specify that the function
Fn depends on if(Unknown). This obeys what the programmer said.

It would be wrong to decide upon one of the two functions and assign
that before scope entry, as it would violate the requirement that
Fn depend on if(Unknown). What's more, even if the condition is
known at compilation time, the programmer is using a construct
that is intrinsically sequential. So regardless of what is known,
the most exact interpretation is still to maintain the sequential
nature. This way you get simple, consistent semantics.

Let's move the function call into a block and have Fn hoist out
of that:

 print (Fn);
 if (Unknown)
 {  Fn();
function Fn() {return 1}   // Hoisted to global scope.
 }

The compiler should do this:

 var Fn; // Hoisted name, assigned the value |undefined|.
 print (Fn);
 if (Unknown)
 {  Fn = HiddenName; // Early assignment at beginning of block.
 Fn();
function HiddenName() {return 1}
 }

(Except the function knows itself as Fn rather than HiddenName.)

Here the name Fn cannot have a value at the beginning of the global
scope, but it can have a value at the beginning of the block where
it's declared-and-defined.

The special treatment of functions, where the compiler moves the
assignment to the beginning of the block, should happen when the
compiler can determine that this is correct, using simple rules,
rules that are simple not only for the compiler but also for the
programmer. In any situation where the compiler can't easily
determine this, it assigns |undefined| at block entry, and then
assigns function object at the spot where the function is defined.

There may be two block-entry points to consider, as above.

Let's hoist with a nonexistent let variable:

 Fn();
 if (Unknown)
 {   let LetVal = 3;  // Nonexistent when Fn() is called.
 if (Maybe)
 {   function Fn()// Hoisting to Outer.
 {   return LetVal;   // Return the let variable.
 }
 }
 }

This is just like the other cases. At the beginning of the global
scope the name Fn exists and has the value

Re: Surprising semantics

2008-07-19 Thread Ingvar von Schoultz
I'm astonished that you interpreted my text in such a weird
way! That's completely foreign to JavaScript!

var is always unconditional in JavaScript. An if() before a
declaration doesn't make the declaration conditional.

The var takes effect long before you reach that if(). It takes
effect before you enter the scope in which the variable resides.
You can consider the declaration glued to the opening brace of
that scope. Or better, glued to both braces and stretched between
them.

The assignment, on the other hand, stays in place and is
conditional. But only the assignment.

With your surprising interpretation things would be much worse
than what your examples suggest. Much worse. Consider assigning
to the unpredictable variable:

 function Outer()
 {
 var Test = 1;
 function Inner()
 {
 if (Unknown)
 var Test = 2; // Weird conditional declaration
 // Many
 // lines
 // of
 // code
 Test = 3;  // Semantics totally unpredictable
 }
 }

Is the outer Test set to three, or has an inner Test been
created that can take the value? Every program would become
impossible to understand, an unintelligible mess.

With my subject line surprising semantics I didn't mean to
advocate more surprises, I wanted less!

I assumed that ES3.1 and ES4 declarations would work like
ES3 declarations do: The declaration spans the entire scope
from before you enter the scope and throughout. The value
is unassigned (undefined) until you reach an assignment.

I took it for granted that making it constant and/or giving it
a type would follow the same pattern. The name gets associated
with the constantness and/or type before you enter the scope,
and it's in effect throughout.

In your examples with dual scopes, the result depends on whether
you mean that the inner c is bound to the outer scope or the
inner. If it's the outer you have conflicting, irreconcilable
bindings that collide before you enter that outer scope, a clear
declaration error. If it's the inner, then the inner declaration
shadows the outer, so the value can be unassigned or 37.

Regarding the plethora of ways to define things, by including
or excluding let you simply change which one of two possible
scope opening braces the declaration is glued to for the purpose
of name visibility. You get the same effect if you just move the
declaration to that spot yourself, without moving the assignment.
By including or excluding let you're simply telling the compiler
where you want this move to go.

Ingvar



Waldemar Horwat wrote:
 We've been down this road before, and the arguments you present have been 
 hashed out over years.  This approach doesn't work.  Read the archives of the 
 ES4 group.
 
 The problem is that you then get a plethora of ways to define things:
 
 var
 const
 function
 type
 namespace
 let
 let const
 let function
 let type
 let namespace
 
 Furthermore, some of them don't make sense (such as function without let) 
 because they can conditionally capture variables that may not even exist.
 
 The example you give of conditional definitions:
 
 if (foo) {
   const c = 37;
 } else {
   const c = abc;
 }
 ... do something with c ...
 
 is particularly disruptive.  You must then support conditional holes:
 
 // outer scope
 function c() ...;
 
 // inner scope
 {
   if (foo) {
 const c = 37;
   }
   ... c can be either 37 or the outer scope function here ...
 }
 
 
 It gets worse:
 
 // 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? ...
 }
 
 
 Also consider:
 
 for (i = 0; i  foo.length; i++) {
   const v = foo[i];
 }
 
 You'll catch everyone off-guard if you make folks do a let const instead of a 
 const here.
 
 
 In E4 it gets worse still because c can have a type:
 
 type c = ...
 {
   if (foo) {
 const c:Number = 37;
   } else if (bar) {
 var c:String = abc;
   }
 }
 ... do something with c, which is either a type, a constant, or a variable, 
 and can be statically typed as either a Number or a String ...
 
 const d:c = ... // Conditional definition requires variable types to be 
 evaluated at run-time, which is not somewhere we want to go in the first 
 version
 
 I don't know of anyone here who wants to support something like that.
 
 Waldemar
 
 
 Ingvar von Schoultz wrote:
 These are some impressions looking at what I expect from the
 language, and how some things in the specification can cause
 confusion.

 I would have contributed here during the discussions, but I
 discovered the mailing lists just a couple of days ago.

 I expect the compiler's interpretation of program-code text
 to be close to my intuitive understanding of what the text
 says. It's very unfortunate if keywords have unexpected
 meanings

Re: Surprising semantics

2008-07-18 Thread Waldemar Horwat
We've been down this road before, and the arguments you present have been 
hashed out over years.  This approach doesn't work.  Read the archives of the 
ES4 group.

The problem is that you then get a plethora of ways to define things:

var
const
function
type
namespace
let
let const
let function
let type
let namespace

Furthermore, some of them don't make sense (such as function without let) 
because they can conditionally capture variables that may not even exist.

The example you give of conditional definitions:

if (foo) {
  const c = 37;
} else {
  const c = abc;
}
... do something with c ...

is particularly disruptive.  You must then support conditional holes:

// outer scope
function c() ...;

// inner scope
{
  if (foo) {
const c = 37;
  }
  ... c can be either 37 or the outer scope function here ...
}


It gets worse:

// 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? ...
}


Also consider:

for (i = 0; i  foo.length; i++) {
  const v = foo[i];
}

You'll catch everyone off-guard if you make folks do a let const instead of a 
const here.


In E4 it gets worse still because c can have a type:

type c = ...
{
  if (foo) {
const c:Number = 37;
  } else if (bar) {
var c:String = abc;
  }
}
... do something with c, which is either a type, a constant, or a variable, and 
can be statically typed as either a Number or a String ...

const d:c = ... // Conditional definition requires variable types to be 
evaluated at run-time, which is not somewhere we want to go in the first version

I don't know of anyone here who wants to support something like that.

Waldemar


Ingvar von Schoultz wrote:
 These are some impressions looking at what I expect from the
 language, and how some things in the specification can cause
 confusion.
 
 I would have contributed here during the discussions, but I
 discovered the mailing lists just a couple of days ago.
 
 I expect the compiler's interpretation of program-code text
 to be close to my intuitive understanding of what the text
 says. It's very unfortunate if keywords have unexpected
 meanings that cause mysterious side effects.
 
 If I learn that ECMAScript will let me change my var(iables)
 into const(ants) I expect this to turn them into constants, in
 the sense that trying to change their value will be considered
 an error. It's very disappointing that by default they are
 instead defined to have the baffling and mysterious behavior
 of silently ignoring an attempt to change them, acting as if
 no error had occurred.
 
 You'll have to keep this oddity in mind at all times, and even
 then errors related to this will sometimes cause symptoms to
 appear far from where the error is, costing quite some time
 to explore. Why doesn't my program change its behavior even
 though I'm provoking changes? Where in this big program's
 complicated sequence of events is the change silently, secretly
 lost?
 
 If instead you use var, at least the problems that can come
 from this will tend to give symptoms closely connected to the
 incorrect change in the value.
 
 So this is a disappointing red flag: Don't use const, it is
 likely to cause baffling problems and unlikely to help.
 
 Unfortunately there's another problem with const that is much
 more important. I often use constants for conditional settings:
 
  if (Debugging)
  {   var DatabaseName = TestDatabase;
  var DisplayCount = 5;
  }
  else
  {   var DatabaseName = RealDatabase;
  var DisplayCount = 15;
  }
 
 The redundant vars are a defensive habit, omitting them would
 be a warning about accesses outside the current scope.
 
 If I haven't been warned, and hear that ECMAScript understands
 const, I expect that replacing var with const will change
 the above from variables into constants. The keyword in no way
 suggests that it will hide them from view. If they disappear
 I'll inevitably consider such a completely unrelated side effect
 a compiler bug.
 
 Because of this I'm unhappy about the conclusions of ES3.1 that
 the visibility scope of const should be the enclosing brace-
 delimited block. Such intricate semantics hidden in words that
 express something completely unrelated will make the language
 seem difficult and fraught with hidden surprises.
 
 I much prefer what ES4 says in various places on the website:
 that you express this localness with let const and let function.
 One block-scope keyword for all block-scope visibility. Consistency
 and clarity.
 
 However this brings me to the unfortunate word let. Although
 this word has a precise and clear technical meaning for the
 initiate, for us in the unwashed masses I can't see that the
 English word let gives even the remotest suggestion of local
 containment. In fact it suggests very clearly that it's related
 to the = 

Surprising semantics

2008-07-17 Thread Ingvar von Schoultz
These are some impressions looking at what I expect from the
language, and how some things in the specification can cause
confusion.

I would have contributed here during the discussions, but I
discovered the mailing lists just a couple of days ago.

I expect the compiler's interpretation of program-code text
to be close to my intuitive understanding of what the text
says. It's very unfortunate if keywords have unexpected
meanings that cause mysterious side effects.

If I learn that ECMAScript will let me change my var(iables)
into const(ants) I expect this to turn them into constants, in
the sense that trying to change their value will be considered
an error. It's very disappointing that by default they are
instead defined to have the baffling and mysterious behavior
of silently ignoring an attempt to change them, acting as if
no error had occurred.

You'll have to keep this oddity in mind at all times, and even
then errors related to this will sometimes cause symptoms to
appear far from where the error is, costing quite some time
to explore. Why doesn't my program change its behavior even
though I'm provoking changes? Where in this big program's
complicated sequence of events is the change silently, secretly
lost?

If instead you use var, at least the problems that can come
from this will tend to give symptoms closely connected to the
incorrect change in the value.

So this is a disappointing red flag: Don't use const, it is
likely to cause baffling problems and unlikely to help.

Unfortunately there's another problem with const that is much
more important. I often use constants for conditional settings:

 if (Debugging)
 {   var DatabaseName = TestDatabase;
 var DisplayCount = 5;
 }
 else
 {   var DatabaseName = RealDatabase;
 var DisplayCount = 15;
 }

The redundant vars are a defensive habit, omitting them would
be a warning about accesses outside the current scope.

If I haven't been warned, and hear that ECMAScript understands
const, I expect that replacing var with const will change
the above from variables into constants. The keyword in no way
suggests that it will hide them from view. If they disappear
I'll inevitably consider such a completely unrelated side effect
a compiler bug.

Because of this I'm unhappy about the conclusions of ES3.1 that
the visibility scope of const should be the enclosing brace-
delimited block. Such intricate semantics hidden in words that
express something completely unrelated will make the language
seem difficult and fraught with hidden surprises.

I much prefer what ES4 says in various places on the website:
that you express this localness with let const and let function.
One block-scope keyword for all block-scope visibility. Consistency
and clarity.

However this brings me to the unfortunate word let. Although
this word has a precise and clear technical meaning for the
initiate, for us in the unwashed masses I can't see that the
English word let gives even the remotest suggestion of local
containment. In fact it suggests very clearly that it's related
to the = that so often follows:

 if (x == 5)
 {   let y = 3;
 }

If x is 5, then let y equal 5. There's an almost inescapably
strong suggestion that let is a phrasing of the assignment
expression, and therefore can't have anything to do with the
braces.

I think ECMAScript should be easily accessible to us in the
unwashed masses. It becomes much more intuitively accessible
if it uses a word that strongly implies localness:

 {   if (x == 5)
 {   local y = 3;
 }
 local const Debugging = false;
 for (local Key in List)
 ++List [Key];
 }

You get plain English sentences that express quite accurately
what they're supposed to mean. The programmer won't be the
least surprised if a value gets hidden by local.

When people want to write let expressions, if they have to
write local instead of let I don't think this will cause
problems. I'm sure the initiate are sophisticated enough that
they can adapt to this.

Apart from this, I think the scoping arrangements would
become significantly simpler and clearer if the language
made a very clear, really visible, intuitively accessible
distinction between two different types of block, and allowed
you to choose either type of block wherever this made sense.

My suggestion is to introduce a clearly distinct new and
better block. This block should be delimited by {{ and }}
if it's at all possible, and I think it is. No keyword,
just {{ and }}. This better block would bind vars, consts
and functions, just like function scopes do. In fact function
scopes and {{ }} would be the same thing, as seen by the
programmer.

An important advantage with {{ }} is that you can keep
everything contained without tedious and error-prone
repetition of local (or let) everywhere. And the scoping
is prominently visible and clearly structured.

It may seem odd that I say that adding yet another scoping