Typing with schemas instead of annotations
Kris Zyp
kris at sitepen.com
Fri Jul 25 08:01:13 PDT 2008
I wanted to propose an alternate approach to defining types within ES. I don't think is actually a realistic proposal for changing ES4 or ES3.1, but more of as an interesting alternate language mechanism for utilizing the ES4 VM with ES3 syntax that I have been interested in exploring for a while now, and wanting to write out in case anyone was interested in the idea. The basic premise is to define Classes, records, and parameter types with a schema object that can easily defined with ES3/JSON, rather than using ES4 type annotation syntax. Type information would be defined with a schema that would act like an interface, and this could be used in combination with local type inference for local variables. My examples and proposal are based on using JSON schema[1] for defining types; my involvement in JSON Schema might preclude objectivity to some degree, but I do think it is the most reasonable ES3 compatible definition for types. Expressed in ES3, it is a simple object structure, with a high degree of correspondence to ES4 types. However, though the proposal is more about using a external type definition/schema even if an alternate format (like Cerny [2]) might be better.
This approach could have application even without actual native support. Development could potentially use this approach to have a codebase that could easily be adapted (possibly through automated transformation, or type omission) to various target ES VMs. Also this approach has nothing to do with "classes as sugar" proposal [3]; it could actually be considered the inverse. Rather than attempting ES4 class syntax with ES3(.1) semantics, it is about using ES3 syntax to drive ES4 class/record semantics. This proposal is also not a complete in it's ability to define ES4 semantics with ES3 syntax. There are definitely plenty of typing forms that this approach can't define, but I would suggest that the majority of type definitions can be defined or inferred with this approach.
Motivation
1. The first motivation is that the code could run in ES3 or ES4 VMs. Of course the ES3 VM doesn't have native typing support, so it would either have to go without typing, or do code translation.
2. Separation of behavior and typing/structure concerns.
a. One can look at the schema for the strucuture and interface of the a given Class or data structure separately from looking at the code for a nice clean, minimal (no annotations) look at the implementation and behavior.
b. One can easily include or exclude typing information for code. Certainly the biggest benefits of the ES4 type system are at development time, with the integrity, correctness, organization, and code completion assistance. Once in production, the majority of web applications spend far more time downloading JavaScript than they do executing it (especially after DOM deductions). Applications may be becoming more sophisticated, but sophisticated apps still increase download time and with ES VMs quickly improving, plus hardware improvements increasing at a faster rate than bandwidth widens, I think download times will continue to dominant execution times for quite a while. Consequently, it seems most likely that performance-savvy developers will mostly like strip the majority of type annotations off of code during compression (perhaps retaining some annotations for performance sensitive hot-spots if VMs prove to benefit from typing information, or retaining it in situations where correct execution depends on type errors, rather than only signalling incorrect execution).
c. Existing applications could be retrofitted with type information without modifying the original code (or minimally modifying). Since type information is provided through a separate schema, the original code can be kept intact.
3. Class reflection has an obvious reification based on the schema. Using JSON Schema, type information is reflected as the schema object itself.
4. Language neutral interfaces - JSON Schema has been designed to be a language agnostic (I realize that might be a little wistful, JSON Schema bears influence from the primitives of JSON and ES, but JSON has proven pretty cross-language capable). One could actually use a JSON Schema defined structure or interface with implementations in various languages. This can even facilitate interaction through non-language-specific means (like RPCs), and JSON Schema is even being used for such with Simple Method Description with Dojo [4].
Example:
First we define the schema interface for our WebMail class, this could actually be in a separate file than the class impl, (note that we are doing it in pure JSON, except for a few comments, lot's of sugar could easily be applied):
Schema.WebMail = {
description:"Webmail Client",
type:"object",
methods:{ // these are the methods for the Webmail class
send: {parameters:[{$ref:"Msg"}]}, // refers to the Msg type defined below
handleMessage: {returns:{$ref:"Msg"}},
fetch: {returns:{$ref:"Msg"}},
get: {parameters:[{type:"integer",name:"id"}], returns:{$ref:"Msg"}}
},
properties:{
database:{
type:"array",
items:{
id:"Msg",
description: "Email message",
properties:{
to:{
type:"array",
items:{$ref:"Addr"} // use the Addr type
},
from:{
id:"Addr",
description: "Email address",
properties:{
at:[{type:"string"},{type:"string"}],
name:{type:"string"}
}
},
subject:{type:"string"},
body:{type:"string"},
read:{type:"boolean"},
messageId:{type:"integer",unique:true,optional:true}
}
}
}
}
};
Now we define the actual class impl, once again this could be in a separate file than the schema:
WebMail = Class(Schema.Webmail,{
send:function (msg) { // msg is determined to be a Msg from the schema
msg.messageId = sendToServer(JSON.encode(msg));
// this is typed check, msg and this.database[n] are both known to be Msgs from the schema
this.database[msg.messageId] = msg;
},
get: function(n) {
if (n in this.database)
// The value of database[n] can be determined from the schema,
// and it properly matches the return type of get
return this.database[n];
// handleMessage also has a return type that matches get, so it is safe
return this.handleMessage(n);
},
showMessage: function(n){
//local type inference can determine the type of msg from the return type of get from the schema:
var msg = this.get(n);
msg.read = 40; // this could statically be determined to be a type conversion error
msg.read = true; // this is a correct assignment
messagePane.innerHTML = msg.body; // can statically be determined to be a string (conversion check not necessary)
},
handleMessage: function(n) {
// this line is tricky, should we auto cast like "wrap"?
var msg = JSON.parse(fetchFromServer(n));
if (msg.result == "no data")
return null;
// if we auto-wrap, the casting can be done on this
// line or the first line depending on local type inferencing
return database[msg.messageId] = msg;
},
database:[]
}
);
This approach creates the class implementation explicitly using the schema, with the schema reference being in the class constructor. One could also use a mechanism where the class loader would use a name-based convention for loading the schemas and connecting them to class constructors. This could be used to facilitate applying schemas/typing to completely pure unaltered ES3 code/files.
This is example is taken from a comparison of ES4 and ES3 (with typing checks) code size [3]. This approach provides an implementation that is more compact than either. Of course, this could be seen as just moving the extra text to another place, the schema, but this is intentional. The schema acts more as documentation, a contract for implementations, generous with descriptions and various other informative (and probably non-normative to the type checker) annotations. Implementations can benefit from minimal code side, and documentation/contract information can be extensively detailed and informative.
Note that it is also important that the schema and implementation be static/declarative (like JSON, no dynamic expressions) in order to be properly statically analyzed. It would be quite easy to create aliases/consts for parts of a schema if you didn't expect it to be shared with other languages/systems.
There would certainly be some interesting issues in exactly how to map a schema to ES4 semantics. I think it would make sense for method-less schemas to generally map to structural types instead of interfaces. Converting plain objects (from functions that return untyped objects) to typed variables and slots would probably require creating a new object with original properties mixed in (like the old wrap proxy). I am sure there would be other issues to solve, and some ES4 type semantics that simply couldn't be described.
Anyway, once again this is not so much a proposal for changing ES, but an exploration of an alternate approach to typing and defining type constraints for various ES VMs. Perhaps this discussion might be better done on LtU, not sure...
[1] http://groups.google.com/group/json-schema / http://groups.google.com/group/json-schema/web/json-schema-proposal---second-draft
[2] http://www.cerny-online.com/cerny.js/documentation/guides/schema
[3] https://mail.mozilla.org/pipermail/es4-discuss/2008-March/002496.html
[4] http://www.sitepen.com/blog/2008/03/19/pluggable-web-services-with-smd/ / http://groups.google.com/group/json-schema/web/service-mapping-description-proposal
Thanks,
Kris
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://mail.mozilla.org/pipermail/es-discuss/attachments/20080725/cfb2de73/attachment-0002.html
More information about the Es4-discuss
mailing list