An experiment using an object literal based class definition pattern

Bob Nystrom rnystrom at google.com
Tue Aug 9 10:23:29 PDT 2011


On Mon, Aug 8, 2011 at 3:14 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:

> function First() {}
> function Second() {ihis.getCompetitor = function() {/*whatever*/*}}
> ...
> First.partner = (new Second).getPartner();    //but getPartner is not
> defined yet
> First. competitor = (new Second).getCompetitor();  //but getCompetitor is
> ok
> Second.prototype.getPartner = function() {/*whatever*/}}
>
> Hosting of function works for resolving references from within function
> bodies.  But individual property initialization expressions for a class  are
> evaluated in order and can have order dependencies.  Function declarations
> don't have any corresponding parts that expose such dependencies but classes
> do.  Hosting does not eliminate those dependencies.
>

All true, and it's good to make sure stuff like this is in the forefront of
our minds.


> How do you simply explain why the initialization of competitor works but
> the initialization but partner does not with out explicitly getting into the
> concept of de-sugaring which arguably is a difficult concept for
> unsophisticated programmers.
>

I think we'll want *both* explanations. For existing JavaScripters, an
explanation of class syntax in terms of desugaring will help the map what
they already know to a new form. But programmers coming to the language cold
shouldn't have to learn the desugared form *first* in order to understand
class syntax, so for them we'll want explanations that just explain the
behavior directly.


>  It's seems much easier to to have a simple rule: must be defined before
> executed. This doesn't really change anything for references nested inside
> function bodies but by this rule, confusing things like the above example
> would be invalid.  If you really wanted to do that you would have to
> manually decompose it.  I'd be fine with that.
>

I'm coming to this thread a bit late, but that sounds reasonable to me.
Mutual recursion of methods within a class is critical. Circular references
between methods of different classes is also vital. Circular references of *
constructor-level* properties seem less important to me. If a user really
wants this:

class A {
class:
  b = new B();
}

class B {
class:
  a = new A();
}

I think it's reasonable to ask them to re-organize stuff a bit.

class A {
}

class B {
}

A.b = new B();
B.a = new A();

Do I understand the issue here, or am I missing some pieces?


> Yes, but by my rule above such a circularity would be an immediate name
> resolution error.  With class hoisting you have to actually analyze the
> inheritance chains to detect the error
>

How does this play with modules? If I have two classes declared in different
modules (which I assume will a common case, if not the most common) then
hoisting or not hoisting won't help either way will it?

I still think the difference is likely to create confusion and I see no need
> to follow Java in this choice of keyword.  "subs" or "subclass"  might be a
> possibility. Or possibly:
>    class superclass <| subclass () {
>   }
>

It would deeply astonish people to have the base class come *before* the
derived class in a class declaration. I didn't even notice that's what you
were doing the first few times I read that. C++, C#, Java, Ruby, Python,
Ada, Dylan, CLOS, Eiffel, Objective-C, and Scala are all subclass-first.

Smalltalk and Self are superclass (or parent in the case of Self) first, but
that's a syntactic side-effect because subclassing is just a message send
and the receiver is on the left. That isn't the case with the syntax we're
proposing here, which is a more Algol-style keyword-first form.


> My main concern is baking time for getting more complex syntactic sugar
> (which, BTW, often ends up being more than just sugar) right.
>

Baking time is a valid concern, and I think one we all share. The only
solution I know of is to get the oven going as soon as we can.

I'm not personally as concerned about classes becoming more than syntactic
sugar. JS already has the semantics needed to get something like classes
working in a really friendly fashion, so adding new actual semantics would
very likely overlap the existing ones in weird and nasty ways. Imagine
trying to cram generic functions into Smalltalk while trying to retain the
existing single-dispatch behavior too. You'd end up with a strange chimera
at best, and more likely just a stuffed jackalope.

I think that alone will keep us honest. We *can't* jam in new dispatch
semantics without making things really painful for ourselves.


> The hoisting issues above are an example of the the sort of complexity that
> need to be worried about.  Simpler  constructors like <| and .{ are easier
> to get right and probably have lower long term impact if we get some detail
> wrong.
>

Simpler constructs are easier to get right, but I think only because they
push the hard work onto our users. If we *only* give them <| and .{, they'll
have to come up with their own patterns to accomplish what they want in
terms of those. Those patterns will be just as hard to get right as it would
be for us to design a syntax directly in the language. The only difference
is that instead of us doing that hard work once (and I'd like to hope we're
the best-qualified to do it!), they'll re-invent it over and over again.

I definitely understand the desire to punt on this, but I think we'd do our
users a disservice if we did. I believe we *can* design a good declarative
syntax for this and that if we do so, we'll make ES a language that's easier
to read and easier to use.

- bob
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20110809/dcd42cfd/attachment.html>


More information about the es-discuss mailing list