Classes as Sugar (was: Renaming ECMAScript 4 for the final standard?)

# Mark S. Miller (17 years ago)

On Sun, Mar 23, 2008 at 11:28 PM, Brendan Eich <brendan at mozilla.org> wrote:

ES4 is not statically typed, so... ... this is a false dilemma.

These analogies are weak and tendentious in my opinion. Let's try to get back to premises and argue forward. Can we start with why you seem to believe that ES4 is statically typed?

The language design I accused of being statically typed is the language design currently called "proposed ES4". Of course, one of the things at stake is whether ES4 will be statically typed. As I've made clear, I hope not.

Now on to your real question. Why do I seem to believe that proposed ES4 is statically typed? A fair question. Proposed ES4 lies somewhere between the simple categories of "statically typed" and "dynamically typed". However, rather than finding a happy compromise between the two, it mostly combines the worst of these two worlds. It pays most of the costs of static typing but obtains few of the benefits. And it inherits all the costs of having been dynamically typed, but retains few of the benefits.

Benefits of Static Typing

  • static assurance that type mismatch errors will not happen at runtime
  • runtime space savings
  • runtime time savings
  • type-based IDE refactoring support (as in IDEA and Eclipse)

Costs of Static Typing

  • language complexity
  • limit expressiveness to statically checkable type "predicates". :int, but no :prime
  • two "expression" languages - static type expressions vs dynamic value expressions
  • multiple partial type theories: nominal, structural, duck, ...
  • verbosity (inside Google, we've expanded to a 100 column limit for Java generics)
  • inappropriate for casual scripting audience

Benefits of Dynamic Typing

Costs of Dynamic Typing

  • runtime space and time costs
  • less static knowledge of dynamic behavior

Benefits of Soft / Gradual Typing

Proposed ES4 and some dynamic languages share some of the advantages of soft typing systems. A soft typing system is, essentially, a dynamic typing system with a convenient syntax for declaring type checks that should be checked at runtime.

  • Better documentation of expected interfaces - better project coordination
  • Fail-fast runtime behavior
  • easy transition from rapid prototypes to production

Benefit of Soft Types absent from Proposed ES4 gradual types:

  • type "predicates" are any runtime test expressible in the language

A comprehensive treatment of all these points would need a book. Here, I will illustrate with a small example as my belated response to Dave Herman.

I wrote:

If instead classes, for example, were defined purely by syntactic expansion to the constructs in ES3.1, then classes would inherit the lexical nestability of the constructs they expand into.

On Tue, Mar 11, 2008 at 5:49 AM, Dave Herman <dherman at ccs.neu.edu> wrote:

Nestability is a good design goal, and I'm glad you brought it up. [...] That said, you've hinted at alternative approaches, perhaps with constructs desugaring to closures of some sort

To get discussion going, I will here present a first cut at a desugaring of something like proposed ES4's class syntax and type declarations into ES3.1 constructs. Were ES4 to be redefined in terms of such syntactic sugar, then the desugared semantics could be precisely ES3.1's would thereby preserve most of ES3.1's virtues. The example below demonstrates the value of preserving ES3.1's lexical nestability and first-classness. Browsers would only need to initially implement ES3.1, and the expansion of ES4 to ES3.1 could happen initially offline or on the server.

                                                     Classes as Sugar

Given something like the createProperty operation we've been discussing lately, and that Lars has made progress on specifying, we could imagine generalizing it to also constrain properties to be non-overridable (as in proposed ES4 "final") and protected (addessable only by way of "this."). We could also imagine an operation constraining an object to be non-extensible (i.e., "fixture"). For purposes of this note, I will make up a plausible static API for these. Were the proposed ES4 class syntax defined in terms of the obvious expansion to ES3.1 + calls to these functions, the first class definition in the proposed ES4 overview doc

class C { var val; var large = Infinity; const x = 3.14; function f(n) { return n+val*2; } }

would expand to

function C() { Object.createProperty(this, 'val', undefined, Readable | Settable); Object.createProperty(this, 'large', Infinity, Readable | Settable); Object.createProperty(this, 'x', 3.14, Readable); Object.fix(this); } Object.createProperty(C, 'prototype', C.prototype, Readable); Object.createProperty(C.prototype, 'f', function(n) {return n+val*2;}, Readable); Object.fix(C.prototype); Object.fix(C);

ES3 constructor functions are already used as, in effect, nominal type identities by ES3 instanceof expressions. By expanding the class syntax to operations that make C's 'prototype' property read-only, we give integrity to this use of nominal typing. The proposed ES4 type declaration syntax could then expand to simple nominal type checks:

var x :C = ...;

expands to

var x = Object.cast(C, ...);

where

Object.cast = function(type, value) {
  if (value instaceof type) { return value; }
  throw new TypeError(...);
}

One further suggestion to make the class syntax more convenient: Get rid of separate constructors. Instead, allow parameters after the class name which are then in scope in the class definition. These would translate directly into the parameters of the constructor function that the class expands to.

With the expansion above, we seem to have only implemented a subset of proposed ES4 providing only nominal types. Sure, it's simpler. But how is it more expressive? Unlike proposed ES4 classes, the expansion above is lexically nestable and multiply instantiable. This provides the flexibility of a runtime trademarking system, where the program can create as many trademarks as are dynamically needed at runtime. Translating Ping's example from www.erights.org/elang/kernel/auditors into an ES4 using this

sugar:

function makeBrand() { var key = {}; class Envelope(payload) { protected const contents = payload; function open(k) { if (k === key) { return this.contents; } } } return { sealer: function(payload) { return new Envelope(payload); }, unsealer: function(env :Envelope) { return env.open(key); } }; }

The key to the correctness of the above code is that every call to makeBrand create a distinct nominal Envelope type, so that the unsealer of a sealer/unsealer pair can use it to check that the alleged envelope argument is not only an instance of the static Envelope code, but is an envelope created by a call to the corresponding sealer.

# Brendan Eich (17 years ago)

On Mar 24, 2008, at 6:45 PM, Mark S. Miller wrote:

Now on to your real question. Why do I seem to believe that proposed ES4 is statically typed? A fair question. Proposed ES4 lies somewhere between the simple categories of "statically typed" and "dynamically typed". However, rather than finding a happy compromise between the two, it mostly combines the worst of these two worlds.

That's a charge you have yet to demonstrate.

It's ironic to read your charge that ES4 is statically typed, just
after reading Matthias Felleisen in Steve Yegge's blog comparing
"Proposed ES4" to TypeScheme and criticizing ES4 because it does not
impose a static type system on JS.

It pays most of the costs of static typing but obtains few of the benefits.

How do you know this?

And it inherits all the costs of having been dynamically typed, but retains few of the benefits.

How did you use "Proposed ES4"? Did you write programs in it?

Benefits of Static Typing

  • static assurance that type mismatch errors will not happen at
    runtime
  • runtime space savings
  • runtime time savings
  • type-based IDE refactoring support (as in IDEA and Eclipse)

Or Flex Builder, which emits a rough subset of "Proposed ES4". Or
once upon a time ASP.NET emitting JScript.NET.

The same retort (we are operating at a crude level of rhetoric here,
almost fact-free, but I'll play along) applies to your other three
bullet points above.

Costs of Static Typing

  • language complexity

And over-minimizing a language imposes a complexity tax on
programmers using it. This happened long ago with JS1 and it was
compounded by foot-dragging monopolist misconduct since 1999.

To decide whether to evolve JS or shrink it, you need only look at
two things: 1) problems JS hackers run into every day, which are not
solved by more idiomatic functional programming hacks that add cycle
and space bloat to their libraries and applications; 2) competition
from other languages in runtimes vying to displace the browser.

  • limit expressiveness to statically checkable type "predicates". :int, but no :prime

Not for want of trying: see

proposals:contracts

Still possible in ES5 after we finish ES4.

I would like to point out that PLT-style contracts are research,
and we've been criticized without justification for "doing research"
in ES4. Why are you promoting contracts when the research has not
been reduced to efficient practice yet, and AFAIK depends on a module
system to keep the contracts on the outside of modules, not on the
inside?

  • two "expression" languages - static type expressions vs dynamic value expressions

That's a fair point. In order to support an optional static
checker, the ES4 design restricts type expressions.

Why this is a hardship has yet to be demonstrated, since your
preferred style of language offers zero type checking at runtime (or
optionally at compile time). If you are arguing the static side of
the equation, please cite statically typed languages that allow full
value expressions as types. I know of research languages, but besides
having undecideable type systems, they are (again) research.

  • multiple partial type theories: nominal, structural, duck, ...

The inclusion of a structural type system is a virtue, since untyped
objects are common on the web even in the face of the nominal DOM and
browser object types, and since no one can revise all code entangled
on the web to use nominal types, at once or even ever.

I don't know what you mean by "duck", it's ill-defined and generally
unsound when used in other languages. Structural types address the
latent typing disciplines used in current Ajax libraries, JSON
schema, etc.

  • verbosity

On the contrary:

// Version 1 of a webmail client, in pure ES3

function send(msg) { validateMessage(msg); msg.id = sendToServer(JSON.encode(msg)); database[msg.id] = msg; }

function fetch() { handleMessage(-1); // -1 means "get new mail" }

function get(n) { if (uint(n) !== n) // JS1: n>>>0 === n throw new TypeError; if (n in database) return database[n]; return handleMessage(n); }

var database = [];

function handleMessage(n) { let msg = JSON.decode(fetchFromServer(n)); if (typeof msg != "object") throw new TypeError; if (msg.result == "no data") return null; validateMessage(msg); return database[msg.id] = msg; }

function validateMessage(msg) { function isAddress(a) { return typeof a == "object" && a != null && typeof a.at == "object" && msg != null && typeof a.at[0] == "string" && typeof a.at[1] == "string" && typeof a.name == "string"; } if (!(typeof msg == "object" && msg != null && typeof msg.id == "number" && uint(msg.id) === msg.id && typeof msg.to == "object" && msg != null && msg.to instanceof Array && msg.to.every(isAddress) && isAddress(msg.from) && typeof msg.subject == "string" && typeof msg.body == "string")) throw new TypeError; }

// ES4 version of same:

type Addr = { at: [string, string], name: string }; type Msg = { to: [Addr], from: Addr, subject: string, body: string, id: uint }; type MsgNoId = { to: [Addr], from: Addr, subject: string, body: string };

function send(msg: like MsgNoId) { msg.id = sendToServer(JSON.encode(msg)) database[msg.id] = msg wrap Msg }

function fetch() handleMessage(-1);

function get(n: uint) { if (n in database) return database[n]; return handleMessage(n); }

function handleMessage(n) { let msg = JSON.decode(fetchFromServer(n)) if (msg is like { result: string } && msg.result == "no data") return null return database[msg.id] = msg wrap Msg }

$ wc /tmp/*way 30 102 688 /tmp/newway 47 177 1302 /tmp/oldway 77 279 1990 total

ES3 code shrinks when rewritten in "Proposed ES4".

(inside Google, we've expanded to a 100 column limit for Java generics)

(What has that to do with anything but Java as used inside Google?
There's no point in arguing about behind-the-firewall non-evidence.)

  • inappropriate for casual scripting audience

ES3 is a subset of "Proposed ES4" and supports evolution and code
migration, along with single specification and implementation
(critical on small-device browsers). Beliefs about "Proposed ES4" and
what is appropriate for "casual scripting audience" members need
demonstration, not assertion.

Benefits of Dynamic Typing

  • lambda abstraction / objects is all you need

We know that lambda is all you need. Why bloat things with objects?
For that matter, why speak English instead of Standard Business
English, the subset Guy Steele made up for his "Growing a Language"
OOPSLA '98 talk, or Esperanto?

Java closures are years late and mega-dollars short of the mark,
compared to first-class functions and closures. Perhaps you agree?

  • all abstractions first-class, composable

That's our goal too, but you'll have to study "Proposed ES4" to show
us where we've failed, instead of citing irrelevant Java non-evidence.

  • simple meta-interpreters can enable powerful meta-programming

I'm not sure what you mean. Could you give an example? I wrote a meta- circular ES3 interpreter in a superset of ES3 (SpiderMonkey extended
to host and boostrap Narcissus), but I don't know what you are
implying cannot be done by ES4, or can be done better, or
exclusively, by ES3 here.

  • syntactic simplicity supports other metatools: minifiers, lints, ...

Syntax is also user interface, and again minimizing it too much makes
programming harder. If this bullet were overriding, we'd be using S- exprs.

  • rapid prototyping

Done all the time using ES3 and "Proposed ES4" approximations and
piece-wise implementations, some already shipped (JS1.8 in Firefox 3
beta), some much closer to ES4 being implemented now.

Costs of Dynamic Typing

  • runtime space and time costs

Space costs are indeed an issue.

I am happy to ignore time costs, and stipulate that they can be
reduced to noise by clever optimization techniques for most cases.

  • less static knowledge of dynamic behavior

A lot less ;-).

Benefits of Soft / Gradual Typing

Proposed ES4 and some dynamic languages share some of the advantages of soft typing systems. A soft typing system is, essentially, a dynamic typing system with a convenient syntax for declaring type checks that should be checked at runtime.

Have you read anything about 'like' types in "Proposed ES4"'s
overview document or evolutionary programming tutorial?

  • Better documentation of expected interfaces - better project
    coordination
  • Fail-fast runtime behavior
  • easy transition from rapid prototypes to production

These bullets all arguably apply to "Proposed ES4".

Benefit of Soft Types absent from Proposed ES4 gradual types:

  • type "predicates" are any runtime test expressible in the language

This is a selective argument, since (a) we have structural types and
'like', which cover common cases (common meaning probably > 80%, but

to be demonstrated, I admit); (b) we can add contracts in the future
if the need arises and the research is "done".

                                                     Classes as  

Sugar

Given something like the createProperty

(defineProperty in the latest spec, FYI.)

we give integrity to this use of nominal typing. The proposed ES4 type declaration syntax could then expand to simple nominal type checks:

var x :C = ...;

expands to

var x = Object.cast(C, ...);

where

Object.cast = function(type, value) {
  if (value instaceof type) { return value; }
  throw new TypeError(...);
}

What is the point of all of this? I can map ES4 to ES3 too, and so
can others -- some are even building translators. But programmers
should (a) not have to do this by hand; (b) not have to procure and
use an offline ES4 to ES3 translator to reap the benefits of ES4,
except for the transparent debugability (instead, they get leaky-unto- flooding-abstraction hell).

One further suggestion to make the class syntax more convenient: Get rid of separate constructors. Instead, allow parameters after the class name which are then in scope in the class definition.

This was considered too, see:

discussion:nullability#example

I like the first part -- constructor parameters in the class head.
The odd-looking special form "function Foo { ... }" for the
constructor code loses compared to an intact constructor function,
though.

With the expansion above, we seem to have only implemented a subset of proposed ES4 providing only nominal types. Sure, it's simpler. But how is it more expressive? Unlike proposed ES4 classes, the expansion above is lexically nestable and multiply instantiable. This provides the flexibility of a runtime trademarking system, where the program can create as many trademarks as are dynamically needed at runtime.

I favor allowing classes in "Proposed ES4" to be nested in functions,
for what it's worth. Since we aren't done yet, perhaps this will come
to pass, although "lexical nestability" is not a burning issue in
general.

"Multiply instantiable" is indeed relevant on the web with various
window objects and browser-specific sandbox objects hosting
instances. We can do better, but so could your example. There's no
reason to force everyone to write, or translate to, a minimally
extended ES3 and suffer the performance and debugging consequences.

The key to the correctness of the above code is that every call to makeBrand create a distinct nominal Envelope type,

In ES4 as proposed you can use eval to make distinct nominal types.

# Thomas Reilly (17 years ago)

Pardon the top post, Brandon already seemed to dig into this proposal with his typical surgical deftness. I just couldn't resist relating some Flash history. As the saying goes, been there, done that. Here's the abridged version of ActionScript's history:

AS1 ~= ES3 AS2 == ES3 + classes as pure compiler sugar AS3 == proto ES4 with classes as first class citizens

Flash is a couple years ahead of the web in terms of dealing with application complexity. If you imagine that the web browser just gave you a Canvas class and JavaScript you'd have a workable approximation of Flash, we have no HTML declarative backbone, its the wild west of scripting. We introduced classes into AS 8 years or so ago in an attempt to deal with the insuitability of ES to these growing applications.

As soon as we introduced classes users clamored for coherent large UI libraries to make their lives easier but any attempt to build a large coherent library brought the VM to its knees. So Flash developers got by with smaller less useful purpose built frameworks. A lot of the problems stemmed from having classes as syntatic sugar. Weird edge cases permeated the system, inheritance, class initialization order, proper statics, and getting overriding working well were issues. But performance and memory usage were the kickers.

We know this because we tried multiple times to build the big honking frameworks in AS2 folks wanted and they ran like poo eating memory and CPU voraciously. So we got wise and built classes into the language as first class citizens with well defined semantics and a compiler/file format that piped all that juicy RTTI to the runtime which sported an optimizing JIT. Then we built an even bigger honking framework and now it all sings:

livedocs.adobe.com/flex/2/langref

Or look at the Android docs, probably similiar in scope/scale. The bits about piping juicy RTTI to the vm probably apply too.

My point is that we needed classes to build these rigid, well defined frameworks that all applications and 3rd party libraries could build on instead of everyone starting with Canvas and all that static information sure doesn't hurt when it comes to performance (can I say that much Brendan? ;-)

The reality is that there's a world of developers out there waiting to build sophisticated web applications but they are limited by the adhoc frameworks at their disposal. The script kiddies would go nuts if they could build full fledged applications in web pages with just a veneer of scripting code on top of a proper UI framework. A least the Flash script kiddies have. Built it and they will come.

I'm not saying AS3 is the be all end all, maybe we drank the Java cool aid a little too much and its great to see ES4 taking its time to get it right (righter?) by being more dynamic/pythonic. The geek in me would love to see classes that could be sliced and diced and julienned (reflected/generated/extended) at runtime like the classes as sugar way would avail but that's not always a good thing. A damn good Singularity [1] paper I read recently made some assertions that dynamic loading makes tricky/impossible proving some type system stability/correctness theorems and I can buy that. A better point is what the geeks want isn't what's necessary good for the rest of the world. Sometimes loosey goosey is good, in large frameworks (and certainly OSes) not so much. We think that one language can serve both ends.

Flex 3 was just released and we say without a doubt that the programming in the large structures in AS3 have done amazing things in scaling the complexity of applications our developers have built. We're building full fledged word processors (buzzword), image editting applications (photoshop express) and even working on viable video editting [2]. The tiny hyper dynamic ninja ES3 ain't gonna fit the bill, somethings gotta give, the web wants heavy artillery (or all the heavy lifting will be left to Silverlight and Flash, oh and I suppose I should say Java, its probably still ahead of Silverlight in some respects). With AS3 we're seeing people build applications with complexity rivaling or surpassing that of the player itself. That never could have happened with AS2 and it won't happen in the browser with ES3.

To come at it from another angle we envision ES4 as playing a similar role at Adobe as python has at Google. Ie something like only write the super critical bits in C/C++ and glue it all together with a higher level dynamic scripting language. Only we're probably drinking the cool aid too much again and see very little need for C/C++ (or at least new C/C++).

Anyways sorry for the rambling but we have some new arrivals and I haven't ranted in awhile. Hopefully this background on how the Adobe folks got here helps somehow. EcmaScript is about more than just the web.

[1] www.research.microsoft.com/os/singularity/publications/OSR2007_RethinkingSoftwareStack.pdf [2] www.riapedia.com/2007/09/07/adobe_photoshop_express_photoshop_with_flex

# Brendan Eich (17 years ago)

On Mar 24, 2008, at 11:21 PM, Thomas Reilly wrote:

... and all that static information sure doesn't hurt when it comes to performance (can I say that much Brendan? ;-)

If you've got it, use it -- no point in dropping type information
during a source to bytecode or other transformation. Just don't
expect the web to type-annotate the way Flash authors did (with some
grumbling) based on the carrot of speed that you guys dangled.

Sometimes loosey goosey is good, in large frameworks (and certainly OSes) not so much. We think that one language can serve both ends.

I'm pretty sure Mark wants to avoid loose geese too, but he has
reached different conclusions. My goal is to work from conclusions
backward to premises such as "ES4 is statically typed" and try to
identify the reason for that kind of statement.

This may not succeed. As Graydon once observed, a lot depends on
"mood" of the language. People see static type checkers (even
optional ones, as optional as JSLint) and type annotations (also
optional), and the 'class' keyword (which is used in dynamic
languages too), and suddenly it's a statically typed language and
therefore b-a-d.

The charge Matthias leveled about intentional loopholes (ES4's *
type, which is the default annotation) meaning errors can (as is
usual in ES3 today) originate from anywhere in a large system and
mess over your hybrid or partially typed code is pretty much on
target. But we are trying not to do Contract or TypeScheme research,
and we are definitely not imposing a static type system (not even
within new modules -- and defining a module is turning out to be
quite hard in JS on the web). Contrary to all the claims alleging
that we are doing those bad things.

If ES4 users want to lock a module down in a static-typing sense,
they can annotate all API entry points with non-like types (wrap
would allow untyped client code to pass plain old objects and arrays,
at a price). Further self-imposed B&D programming inside the module
is optional in my view. This is the best that I believe we can do for
ES4. It beats purely dynamic typing for any non-trivially-small
codebase. I wish I had the option to code in this language when
writing Narcissus.

# Thomas Reilly (17 years ago)

If ES4 users want to lock a module down in a static-typing sense, they can annotate all API entry points with non-like types (wrap would allow untyped client code to pass plain old objects and arrays, at a price). Further self-imposed B&D programming inside the module is optional in my view. This is the best that I believe we can do for ES4. It beats purely dynamic typing for any non-trivially-small codebase. I wish I had the option to code in this language when writing Narcissus.

And I think that's good enough. Flex has a "strict" mode where you get a warning if you don't statically type things. Typically I turn it off when prototying around and turn it back on if I'm implementing anything others have to use. I never really stopped to think why this is. Is it as simple as facilitating code sharing and declarative input validation or is there more to it? I suspect it might be that simple.

One thing that occurs to me is that classes facilitate wrapping up code into a compiled form and sharing it as a fixed closed entity. Not sure that will ever apply to browsers but its a very common idiom in Flex (and Java/C/C++...). Here static typing is just facilitating creating code boundaries I guess.

Boundaries are definitely a good thing when trying to coordinate the efforts of many. But then I see things like this:

ptrthomas.wordpress.com/2006/06/06/java-call-stack-from-http-upto-jdbc-as-a-picture

Its kinda awe-inspiring to look at but part of me wonders if maybe there's a better way (and wow, the JVM must have an unbounded stack)? Maybe with ES4 all the boxes are there with similiarly defined boundaries but the meat inside the boxes is leaner/clever dynamic code. How's that for an ES4 pitch, like Java but with leaner meat ;-) Taste great, less filling.