Classes as Sugar -- old threads revisited

Mark S. Miller erights at google.com
Mon Mar 30 19:40:29 PDT 2009


At the Harmony portion of the recent EcmaScript meeting, we took up
the classes as sugar discussion. For background, the relevant recent
threads on es-discuss are:

"Look ma, no this" thread starting at
https://mail.mozilla.org/pipermail/es-discuss/2008-November/008181.html

"How much sugar do classes need?" thread starting at
https://mail.mozilla.org/pipermail/es-discuss/2008-November/008181.html
especially Peter Michaux's
https://mail.mozilla.org/pipermail/es-discuss/2008-November/008185.html

"Block exprs as better object literals" thread starting at
https://mail.mozilla.org/pipermail/es-discuss/2008-December/008521.html
continuing in January starting at
https://mail.mozilla.org/pipermail/es-discuss/2009-January/008525.html

The discussion was a bit frustrating because the committee was largely
unaware of the last two of these threads. Although I do recommend
reviewing them, I will here restate and refine my last suggestion in
a self contained pair of messages. Again, I would like to thank Peter
Michaux for the general approach.

Since the proposal I'm about to make will be in terms of a desugaring
to other elements of ES-Harmony, In this first message I will recap
and speculate on the needed elements of ES-Harmony. Since "lambda" is
still controversial, I will avoid it. But I will introduce instead the
minimal replacement I still need -- a "let" expression which respects
Tennent Correspondence. I would hope that this "let" actually desugars
to "lambda", but I do not assume this here. If we do adopt "lambda",
then all the desugaring I present to functions should instead be to
lambdas.

We need to refactor the ES5 grammar a bit. ES5 has two statement-level
declaration productions, VariableStatement and FunctionDeclaration. In
ES5, VariableStatement is included in Statement, whereas
FunctionDeclaration is included in SourceElement, which reads

  SourceElement: // ES5
    Statement
  | FunctionDeclaration

The reason is that ES5 prohibits FunctionDeclarations in nested
blocks, permitting them only at the top level of Program and
FunctionBody.

Since ES-Harmony will allow lexically nested FunctionDeclarations as
well as "let" and "const" declarations, all with proper block-level
lexical scope, let's rename VariableStatement to VariableDeclaration
and refactor the grammar as:

  Declaration:
    VariableDeclaration
  | FunctionDeclaration
  | ConstFunctionDeclaration
  | LetDeclaration
  | ConstDeclaration
  | ClassDeclaration

with ClassDeclaration explained in the next message. The important
point for now is that ClassDeclaration desugars to, in effect, declare
a function. Like a harmonious FunctionDeclaration, the name declared
by a ClassDeclaration has proper block-level lexical scope, and the
initialization of this name to the function is hoisted to the
beginning of the block so no uninitialized state is observable.

  LetDeclaration:
    "let" Identifier (":" Expression)_opt "=" Expression

  ConstDeclaration:
    "const" Identifier (":" Expression)_opt "=" Expression

The optional (":" Expression) is for dynamic type checking, where the
expression is evaluated to a guard value in the current lexical scope,
and that guard value is somehow used to represent type-like
constraints on the values that may be bound to the variable it guards.


  Statement:
    // current Statement contents without VariableStatement

  SourceElement:
    Statement
  | Declaration

  Block:
    "{" SourceElements_opt "}"

  MemberExpression:
    // current MemberExpression contents
  | LetExpression
  | ObjectExpression

with ObjectExpression explained in the next message.

  LetExpression:
    "let" Bindings_opt "{" SourceElements_opt Expression "}"

For purposes of this note, we can assume Bindings_opt is
absent. Likewise, this note has no need for a LetStatement.

The semantics of the LetExpression (due, IIRC, to a suggestion of Dave
Herman) is to evaluate the parts between the curlies as a nested
block, where the value of the LetExpression is the value of the
terminal Expression in its body.


The ConstFunctionDeclaration above elaborates on someone's suggestion,
I forget who, for a syntax like:

  ConstFunctionDeclaration:
    "const" Identifier "(" FormalParameterList_opt ")" "{" FunctionBody "}"

This is just like the FunctionDeclaration syntax except for the use of
"const" in the position where "function" normally appears. Like a
FunctionDeclaration and a ClassDeclaration, a ConstFunctionDeclaration
declares a block-scoped hoisted function. For example,

  const foo(p1, p2) { body; }

desugars to

  const foo = Object.freeze(function foo(p1, p2) { body; });
  Object.freeze(foo.prototype);

where this pair is hoisted to the top of its enclosing block. It would
have been more elegant if we could have desugared directly to a
FunctionDeclaration, in order to reuse the latter's hoisting
machinery. However, we can't since the variable introduced by
FunctionDeclaration is mutable ("let"-like) and there's no way to get
the freezing of the function or its prototype to be automagically
hoisted as well. Hopefully the introduction of "lambda" will provide a
more elegant way to address this need. If neither lambda nor
ConstFunctionDeclaration are accepted into ES-Harmony, then consider
the latter as only an explanatory device for other desugarings to be
presented shortly.

-- 
    Cheers,
    --MarkM


More information about the Es-discuss mailing list