Part 2 of 2
Since a class declaration is like a function declaration, we use a similar syntax: ClassDeclaration: "class" Identifier "(" FormalParameterList_opt ")" "{" ObjectBody "}" In this note, we explain the desugaring of ClassDeclaration via an intermediate desugaring to ObjectExpression. ObjectExpression: "object" ("implements" Expression)_opt "{" ObjectBody "}" where class Foo(p1, p2) { ... } desugars to const Foo(p1, p2) { return object implements Foo { ... }; } which desugars to const Foo = Object.freeze(function Foo(p1, p2) { return object implements Foo { ... }; }); As with ConstFunctionDeclaration, if ObjectDeclaration is not accepted into ES-Harmony, then consider it only an explanatory device. ObjectBody: Statement | Declaration | "public" Declaration | "public" Identifier (":" Expression)_opt "=" Expression | "public" Identifier "(" FormalParameterList_opt ")" "{" FunctionBody "}" where public x :T = y; desugars to public const x :T = y; and public foo(p1, p2) { body; } desugars to public const foo(p1, p2) { body; } Revisiting Peter's example, class Point(privX, privY) { let privInstVar = 2; const privInstConst = -2; public toString() { return ('<' + getX() + ',' + getY() + '>'); }; public getX() { return privX; }; public getY() { return privY; }; public let pubInstVar = 4; public pubInstConst = -4; } desugars to const Point(privX, privY) { return object implements Point { let privInstVar = 2; const privInstConst = -2; public toString() { return ('<' + getX() + ',' + getY() + '>'); }; public getX() { return privX; }; public getY() { return privY; }; public let pubInstVar = 4; public pubInstConst = -4; }; }); which desugars to a hoisted const Point = Object.freeze(function(privX, privY) { return object implements Point { let privInstVar = 2; const privInstConst = -2; public const toString() { return ('<' + getX() + ',' + getY() + '>'); }; public const getX() { return privX; }; public const getY() { return privY; }; public let pubInstVar = 4; public pubInstConst = -4; }; }); Object.freeze(Point.prototype); The remaining elements needing explanation, ObjectExpression, ObjectBody, and "public" Declaration desugar together into a LetExpression whose final expression is created by gathering together representatives of the "public" declarations. The intent of the "implements" clause is that the value of the object expression be tagged somehow with an unforgeable nominal type, such that this value is able to pass the corresponding guard. For now, I will take a shortcut and assume that when a guard-value is a function, that the dynamic type-like test is approx isFrozenProp(guard, 'prototype') && (specimen instanceof guard) If the function's 'prototype' property is frozen, then instanceof is at least a monotonic test. However, it is effectively forgeable -- it guarantees no useful property -- since anyone may create an object that passes this test but has arbitrarily weird behavior. (Thanks to Waldemar for emphasizing this point at our last meeting.) In order to have a high integrity desugaring of ClassDeclaration or ObjectExpression, we need better lower level support for some kind of trademarking mechanism. We will need to revisit this issue, but not in this note. With this caveat, our example further desugars to const Point = Object.freeze(function(privX, privY) { return let { // hoisted functions first const toString = Object.freeze(function() { return ('<' + getX() + ',' + getY() + '>'); }); Object.freeze(toString.prototype); const getX = Object.freeze(function() { return privX; }); Object.freeze(getX.prototype); const getY = Object.freeze(function() { return privY; }); Object.freeze(getY.prototype); let privInstVar = 2; const privInstConst = -2; let pubInstVar = 4; const pubInstConst = -4; Object.freeze(Object.create(Point.prototype, { toString: {value: toString}, getX: {value: getX}, getY: {value: getY}, pubInstVar: {get: Object.freeze(function{return pubInstVar;}), enumerable: true}, pubInstConst: {value: pubInstConst, enumerable: true} })) }; }); Object.freeze(Point.prototype); Actually, I cheated above. Notice the lack of an "enumerable: true" in the properties representing toString, getX, and getY. Rather than consider "public" Identifier "(" FormalParameterList_opt ")" "{" FunctionBody "}" equivalent to "public" "const" Identifier ... consider it instead to be almost identical but representing a method definition. As a method definition, it makes sense (to me at least) to suppress its enumerability. By considering this syntactic form to represent a distinct method definition production, we can almost cleanly address another of Waldemar's concerns. Within the FunctionBody of a method production, we can rename all free "this"s to refer to the object being made. For example object { public getMe() { return this; }} could desugar to let { const t1 = object { public getMe() { return t1; }}; t1 } where t1 is a variable name not otherwise used in the ObjectExpression. This would desugar to let { const t1 = let { const getMe = Object.freeze(function getMe() { return t1; }); Object.freeze(getMe.prototype); Object.freeze(Object.prototype, { getMe: {value: getMe} }) } t1 } The remaining problem left unaddressed by this proposal is that it creates an unmet need for an analogous private method production, where "this" is analogously renamed. -- Cheers, --MarkM _______________________________________________ Es-discuss mailing list Es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss