|
View:
New views
7 Messages
—
Rating Filter:
Alert me
|
|
|
Classes as Sugar -- old threads revisitedAt 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 _______________________________________________ Es-discuss mailing list Es-discuss@... https://mail.mozilla.org/listinfo/es-discuss |
|
|
Re: Classes as Sugar -- old threads revisitedPart 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@... https://mail.mozilla.org/listinfo/es-discuss |
|
|
Re: Classes as Sugar -- old threads revisitedMark,
Thanks for clarifying this design. One important issue to consider is the goal of classes as high-integrity, unforgeable objects. It seems the desugaring of "class Foo" to "object implements Foo" loses this ability ... > class Foo(p1, p2) { ... } > > desugars to > > const Foo(p1, p2) { > return object implements Foo { ... }; > } since other code can later do: object implements Foo { ... weird behavior here ... } Is there some way to strengthen this behavior? (some other comments are included below.) - Cormac > > which desugars to > > const Foo = Object.freeze(function Foo(p1, p2) { > return object implements Foo { ... }; > }); Plus: Object.freeze(Foo.prototype); right? > > As with ConstFunctionDeclaration, if ObjectDeclaration is not accepted You mean ObjectExpression, IIUC. > 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@... > https://mail.mozilla.org/listinfo/es-discuss > es-discuss mailing list es-discuss@... https://mail.mozilla.org/listinfo/es-discuss |
|
|
Re: Classes as Sugar -- old threads revisitedMark,
Thanks for clarifying this design. One important issue to consider is the goal of classes as high-integrity, unforgeable objects. It seems the desugaring of "class Foo" to "object implements Foo" loses this ability ... > class Foo(p1, p2) { ... } > > desugars to > > const Foo(p1, p2) { > return object implements Foo { ... }; > } since other code can later do: object implements Foo { ... weird behavior here ... } Is there some way to strengthen this behavior? (some other comments are included below.) - Cormac > > which desugars to > > const Foo = Object.freeze(function Foo(p1, p2) { > return object implements Foo { ... }; > }); Plus: Object.freeze(Foo.prototype); right? > > As with ConstFunctionDeclaration, if ObjectDeclaration is not accepted You mean ObjectExpression, IIUC. > 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@... > https://mail.mozilla.org/listinfo/es-discuss > es-discuss mailing list es-discuss@... https://mail.mozilla.org/listinfo/es-discuss |
|
|
Re: Classes as Sugar -- old threads revisitedThese two are reversed, aren't they? The const should have the getter
rather than the value? On 2009-03-30, at 22:41EDT, Mark S. Miller wrote: > pubInstVar: {get: Object.freeze(function{return pubInstVar;}), > enumerable: true}, > pubInstConst: {value: pubInstConst, > enumerable: true} _______________________________________________ es-discuss mailing list es-discuss@... https://mail.mozilla.org/listinfo/es-discuss |
|
|
Re: Classes as Sugar -- old threads revisitedOn Tue, May 26, 2009 at 3:08 PM, P T Withington <ptw@...> wrote: These two are reversed, aren't they? The const should have the getter rather than the value? No, it's correct as is. With an initialized const variable, its value can't change, so we can just present the value directly as the value of a frozen data property. A let variable, on the other hand, can continue to change. Thus, in order for the property to continue to present the variable's current value we need a getter. -- Cheers, --MarkM _______________________________________________ es-discuss mailing list es-discuss@... https://mail.mozilla.org/listinfo/es-discuss |
|
|
Re: Classes as Sugar -- old threads revisitedOn Tue, May 26, 2009 at 2:49 PM, Cormac Flanagan <cormac@...> wrote:
> Mark, > > Thanks for clarifying this design. > > One important issue to consider is the goal of classes as > high-integrity, unforgeable objects. > It seems the desugaring of "class Foo" to "object implements Foo" > loses this ability ... > >> class Foo(p1, p2) { ... } >> >> desugars to >> >> const Foo(p1, p2) { >> return object implements Foo { ... }; >> } > > since other code can later do: > > object implements Foo { ... weird behavior here ... } Yes, as explained: On Mon, Mar 30, 2009 at 7:41 PM, Mark S. Miller <erights@...> wrote: 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. > Is there some way to strengthen this behavior? AFAICT, not using only the mechanisms found in ES5. Yes, given aforementioned support for some kind of new primitive trademarking mechanism, as found in Gedanken <http://www.erights.org/history/morris73.pdf> or W7 <http://mumble.net/~jar/pubs/secureos/>. Section 6.3 & Figure 6.6 of <http://erights.org/talks/thesis/> explain trademarking in E as a step towards E's auditors <http://erights.org/elang/kernel/auditors/>, <http://wiki.erights.org/w/index.php?title=Guard-based_auditing>, which we may want to consider. >> which desugars to >> >> const Foo = Object.freeze(function Foo(p1, p2) { >> return object implements Foo { ... }; >> }); > > Plus: > > Object.freeze(Foo.prototype); > > right? Yes, good catch! >> >> As with ConstFunctionDeclaration, if ObjectDeclaration is not accepted > > You mean ObjectExpression, IIUC. Oops. Yes. -- Cheers, --MarkM _______________________________________________ es-discuss mailing list es-discuss@... https://mail.mozilla.org/listinfo/es-discuss |
| Free embeddable forum powered by Nabble | Forum Help |