An experiment using an object literal based class definition pattern

Allen Wirfs-Brock allen at wirfs-brock.com
Mon Aug 8 15:14:50 PDT 2011


On Aug 8, 2011, at 1:42 PM, Brendan Eich wrote:

> On Aug 5, 2011, at 3:29 PM, Allen Wirfs-Brock wrote:
>> 
>> 
>> I think hoisting classes may be problematic anyway.
> 
> That would be surprising, if true, since classes are sugar for prototypal inheritance based on function *declarations*, which hoist.

Let me try a more carefully constructed example:

class First() {
class:
  partner = (new Second).getPartner();
  competitor = (new Second).getCompetitor();
}
class Second() {
  constructor() {
     this.getCompetitor = function() {/*whatever*/*}
  }
  getPartner() {/*whatever*/}}
}

which viewed using the obvious  desugaring with hosting ould presumably be:

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.  

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.  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.
> ...
> 
>> or even inheritance circularities.
> 
> Circularities are errors in any system.
> 

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


> 
>> It isn't obvious to me that there is any actual utility in hoisting class declarations.
> 
> It's the same utility for hoisting function declarations: letrec-style binding for mutual recursion, and in general, freedom to use top-down or any order when declaring functions forming a callgraph, instead of having to topologically sort based on caller->callee relation and write function declarations in reverse order.

The name resolution rules we agreed to permit mutually recursive functions without hoisting (or you could view those rules as hoisting everything):

let f1 = function() {f2()};
let f2 = function() {f1()};
f1();  //happily recurs until it it runs out of stack

and hoisting doesn't prevent this sort of error with tail-end function declarations:

f1();  //error because foo not yet initialized;
...
let foo="foo"
function f1() {print(foo)};

The above is essentially the situation that occurs with property initialization expressions.

> 
>> (BTW, does it bother anybody that the meaning of "extend" to mean "subclass of" in such class declarations is quite different from what seems to be the most common meaning (add properties to an object) of a function named "extend" in JS libraries and frameworks.)
> 
> Slightly. The words differ. Class syntax uses 'extends', Prototype et al. use 'extend'.
> 
> Extension by copying vs. extension by delegation are two varieties of extension. Some call the copying form "composition".

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 () {
  }


> 
>>> 3. As you already mentioned contra Axel, one cannot transpose .prototype. and .constructor. parts. Worse, if you don't have class methods, you have to end with a runt .constructor followed by a ;.
>> 
>> My argument is that once you learn the pattern this isn't an issue.
> 
> My argument is simply that class syntactic sugar can be considered on top of <| and .{ -- there is no "exclusive OR" mandate here. Could be "AND also" ;-).

My main concern is baking time for getting more complex syntactic sugar (which, BTW, often ends up being more than just sugar) right.  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.

Allen




> 
> /be
> 

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


More information about the es-discuss mailing list