delegating to typed objects

# Kris Zyp (18 years ago)

It appears (at least in the reference implementation) that one there is an object that has a delegate object that is a typed object, that overwriting members in the top instance is sometimes not possible. For example: class C { var x : String; } c = new C; function F() {} F.prototype=c; f = new F; // f.proto = c f.x =4; If you do this c.x now equals "4", that is the value is not set in f, but it goes up the prototype chain and sets 4 into x (and does implicit conversion). I think I realize why this is being done. c is of type C, but f is not of type C, but it still must maintain consistency in the typing of its members (so instance method can be assured of the right types, I presume). However, this seems that like quite unintuitive behavior for JavaScript. Generally one would expect the statement f.x=4; to only affect f, not f's delegate (c). Was it ever considered to enforce a system where if f delegates to c, that f must be the same type (or subtype) as c? This could be done by allowing [[Class]] definition to be inheritable from a delegate (in this f would not define what [[Class]] it is, but would inherit it's class definition from c which defines it's class to be C), and maintaining prototype chain consistency with class inheritance. Sorry for all the questions, but now that we have a reference implementation, lots of questions come up... Kris

# Brendan Eich (18 years ago)

On Jul 2, 2007, at 9:21 AM, Kris Zyp wrote:

It appears (at least in the reference implementation) that one
there is an object that has a delegate object that is a typed
object, that overwriting members in the top instance is sometimes
not possible. For example: class C { var x : String; } c = new C; function F() {} F.prototype=c; f = new F; // f.proto = c f.x =4; If you do this c.x now equals "4", that is the value is not set in
f, but it goes up the prototype chain and sets 4 into x (and does
implicit conversion). I think I realize why this is being done. c
is of type C, but f is not of type C, but it still must maintain
consistency in the typing of its members (so instance method can be
assured of the right types, I presume). However, this seems that
like quite unintuitive behavior for JavaScript. Generally one would
expect the statement f.x=4; to only affect f, not f's delegate (c).
Was it ever considered to enforce a system where if f delegates to
c, that f must be the same type (or subtype) as c? This could be
done by allowing [[Class]] definition to be inheritable from a
delegate (in this f would not define what [[Class]] it is, but
would inherit it's class definition from c which defines it's class
to be C), and maintaining prototype chain consistency with class
inheritance.

If you want x to be a delegated and override-able "plain old"
property, not a fixture, declare C thus:

class C { prototype var x : String; }

Without prototype qualifying var, you get a fixture, and fixtures are
always fixed as to meaning and type constraint by type of their
containing class. That's their raison d'être.

Having said that, I'll admit that your suggested change to the class
instantiated by (new F) given F.prototype = new C is interesting and
provocative. By default, ES4 as reference-implemented follows ES3 and
makes (new F) for all functions F creates a new Object instance. But
native constructor functions and classes can make instances of
narrower types than Object, obviously (Date, RegExp, etc.). And some
built-in classes (at least RegExp per ES3's spec, although no
browsers follow this) have prototype properties of type Object.

So there's an attractive symmetry in making F.prototype = new C cause
(new F) to instantiate C instances.

If we did this, you would still have fixtures overriding prototype
properties, but you would have a fixture per (new F) instance, not
one in the (new C) prototype instance as in your example (the one
denoted by the variable |c|). That would avoid the pigeon-hole
problem. (You could also use the prototype qualifier as in my counter- example.)

If we did this, you might also (or might not) want |dynamic| in front
of class C {...} in order to allow "expandos".

Still thinking, comments welcome.

# Kris Zyp (18 years ago)

To me it makes more sense to have functions constructors creating new instances of classes as you described. It seems more consistent having prototype chains and class inheritance being in sync as much as possible, as they are both forms of inheritance. Right now, if an object is an instance of a class it has the classes's prototype in the instance's prototype chain. If D extends C, than instances of D have both C and D's prototype in the prototype chain. It would really be nice if the converse were true, that is if an object has C's prototype in it's prototype chain than it must be an instance of C. Of course, this would not only be more consistent logically because both forms of inheritance (class and prototype) would have the same hierarchy, but it seems more consistent with ES3 behavior where instanceof determinations are based upon prototype chains. "is" and "instanceof" would have more similiar behavior (albiet still different). I suppose if proto was still exposed as writable property programmers could still mess up the whole consistency though. Kris

# Brendan Eich (18 years ago)

On Jul 9, 2007, at 12:10 PM, Jeff Dyer wrote:

class C { var x : String; } c = new C; function F() {} F.prototype=c; f = new F; // f.proto = c f.x =4;

If you do this c.x now equals "4", that is the value is not set
in f, but it goes up the prototype chain and sets 4 into x (and
does implicit conversion).

This behavior is incorrect. For compatibility with ES3, property
writes must be shallow.

Agreed, but the question is: how? If you say that f.hasOwnProperty ('x'), then what is the type constraint on that property's value? If
it is String, then how is it implemented given that new F creates an
Object instance, not a C instance?

One could imagine a fixture being added to each Object instance
created by new F. It would make the type of f be {x:String}, which is
compatible with C assuming you put 'public' in front of 'var
x' (that's necessary, otherwise the x in c is in C's internal
namespace -- please correct me if I have this wrong).

What's not allowed is an unconstrained slot on f -- that would break
type safety for methods of C that count on x being a String. What
also seems hard to believe in is some magic getter/setter pair in c
(f.proto) that lets x be stored in each instance (f) but enforces
the type constraint.

So when you wrote "This behavior is incorrect", did you have in mind
a fixture per instance, created on demand when assigning ("writes
must be shallow") to x where f.proto.x is a fixture defined by C?
I.e., a copy-on-write fixture due to the class of f differing (and
lacking any public::x of its own) from the class of c?

If you want x to be a delegated and override-able "plain old"
property, not a fixture, declare C thus:

class C { prototype var x : String; }

To be clear, in the example above this would put a expando property
‘x’ on ‘f.proto.proto’.

Yes, but it could be shadowed in f instances, inheriting the type
constraint.

See my comment before my last one. Making prototypes more like
classes would make them less flexible, more complicated, and
duplicate functionality that is already available. I don’t see the
point, but maybe I’m missing the use case.

Using functions as extra constructors may be a use-case, but the
primary problem is definitional: given functions and classes, what
should happen in Kris's example, and how should it be specified/ reference-implemented? Copy-on-write fixtures are a new thing under
our Sun, it seems to me.

# Jeff Dyer (18 years ago)

On 7/9/07 1:17 PM, Brendan Eich wrote:

On Jul 9, 2007, at 12:10 PM, Jeff Dyer wrote:

class C { var x : String; } c = new C; function F() {} F.prototype=c; f = new F; // f.proto = c f.x =4;

If you do this c.x now equals "4", that is the value is not set in f, but it goes up the prototype chain and sets 4 into x (and does implicit conversion).

This behavior is incorrect. For compatibility with ES3, property writes must be shallow.

Agreed, but the question is: how? If you say that f.hasOwnProperty('x'), then what is the type constraint on that property's value? If it is String, then how is it implemented given that new F creates an Object instance, not a C instance?

Œx¹ is an expando on Œf¹ and therefore has no type constraint. Except for the assigment to Œf.x¹, the value of f.hasOwnProperty(Œx¹) would be false.

One could imagine a fixture being added to each Object instance created by new F. It would make the type of f be {x:String}, which is compatible with C assuming you put 'public' in front of 'var x' (that's necessary, otherwise the x in c is in C's internal namespace -- please correct me if I have this wrong).

What's not allowed is an unconstrained slot on f -- that would break type safety for methods of C that count on x being a String. What also seems hard to believe in is some magic getter/setter pair in c (f.proto) that lets x be stored in each instance (f) but enforces the type constraint.

But it could be allowed, just as the following is allowed when a method m is added to the class C defined above,

var o = {} o.g = F.prototype.m o.g ()

Œthis¹ is bound to Œc¹ in Œg¹.

So when you wrote "This behavior is incorrect", did you have in mind a fixture per instance, created on demand when assigning ("writes must be shallow") to x where f.proto.x is a fixture defined by C? I.e., a copy-on-write fixture due to the class of f differing (and lacking any public::x of its own) from the class of c?

I had in mind that Œf.x¹ is an unconstrained expando property.

If you want x to be a delegated and override-able "plain old" property, not a fixture, declare C thus:

class C { prototype var x : String; }

To be clear, in the example above this would put a expando property Œx¹ on Œf.proto.__proto__¹.

Yes, but it could be shadowed in f instances, inheriting the type constraint.

See my comment before my last one. Making prototypes more like classes would make them less flexible, more complicated, and duplicate functionality that is already available. I don¹t see the point, but maybe I¹m missing the use case.

Using functions as extra constructors may be a use-case, but the primary problem is definitional: given functions and classes, what should happen in Kris's example, and how should it be specified/reference-implemented? Copy-on-write fixtures are a new thing under our Sun, it seems to me.

See my comments above. There is no need for copy on write fixtures, just a plain old expando on Œf¹ for Œx¹. If you want type guarantees, use classes.

# Kris Zyp (18 years ago)

It seems to me that transfering bound methods to the function instances makes the situation equally confusing: Now if write: class C { var x : String; function m() : String { return this.x }} c = new C; function F() {} F.prototype=c; f = new F; // f.proto = c f.x =4; f.m() -> ???

A Transferred bound method would mean that when we call f.m(), that the "this" in the method execution is actually c and not f. Does that really make sense? Kris

# Jeff Dyer (18 years ago)

On 7/9/07 2:42 PM, Kris Zyp wrote:

It seems to me that transfering bound methods to the function instances makes the situation equally confusing: Now if write: class C { var x : String; function m() : String { return this.x }} c = new C; function F() {} F.prototype=c; f = new F; // f.proto = c f.x =4; f.m() -> ??? A Transferred bound method would mean that when we call f.m(), that the "this" in the method execution is actually c and not f. Does that really make sense?

Agreed. This presents a usability hazard. It has been suggested that if Œf¹ is not compatible with class C, then an error should be raised. This might be an alternative that avoids the copying of fixtures or other class like inheritance mechanisms to ensure type safety.

I agree with Brendan that there has to be a rational explanation (and reference implementation) of the behavior of this feature, but if there are only edge use cases punting with an exception might be just fine.

# Brendan Eich (18 years ago)

On Jul 9, 2007, at 2:19 PM, Jeff Dyer wrote:

See my comments above. There is no need for copy on write
fixtures, just a plain old expando on ‘f’ for ‘x’.

Ok, I see your point.

If you want type guarantees, use classes.

Rather, use classes or structural types only, not constructor
functions that delegate to class instances. Or if using constructor
functions that delegate to class instances, use prototype methods in
the class to unbind |this|, and make sure those methods either (a)
are generic or (b) enforce the necessary |this| type constraints.

A bit of a mouthful compared to your statement. :-/

# Kris Zyp (18 years ago)

Is class like inheritance just too much of an implementation burden? Is it hard for new F objects to be an instantiation of C like subtypes of C would be? It stills seems to me consistency with the behavior of the rest of ES would suggest class like inheritance. If an error was to be thrown, where would the error occur in this example? Kris

# Brendan Eich (18 years ago)

On Jul 9, 2007, at 3:22 PM, Jeff Dyer wrote:

On 7/9/07 2:42 PM, Kris Zyp wrote:

It seems to me that transfering bound methods to the function
instances makes the situation equally confusing: Now if write: class C { var x : String; function m() : String { return this.x }} c = new C; function F() {} F.prototype=c; f = new F; // f.proto = c f.x =4; f.m() -> ??? A Transferred bound method would mean that when we call f.m(),
that the "this" in the method execution is actually c and not f.
Does that really make sense?

Agreed. This presents a usability hazard. It has been suggested
that if ‘f’ is not compatible with class C, then an error should be
raised.

Kris asks where such an error would be raised. I would be surprised
if it were anywhere other than F.prototype = c, but that is odd given
that one can write ES3 code that sets, e.g. F.prototype = new Date.

This might be an alternative that avoids the copying of fixtures or
other class like inheritance mechanisms to ensure type safety.

In your "f.x expando w/o type constraint shadows c.x" counter- proposal, there's no type safety problem for fixed methods of C --
they can't be extracted without remembering their |this| binding, and
when called (e.g., f.m()) on an incompatible type you should get a
type error because |this| is type-constrained (as well as instance- bound) for fixed methods. Prototype methods as always have to
constrain the type of |this|, or leave it * and do their own generic
programming or checking.

I think we have only two courses:

  1. Kris's proposal to make F construct instances of C. If C is not
    dynamic, these instances can't be decorated with expandos by (new F)
    or its callers.

  2. Your proposal to avoid any magic implication of F.prototype = c,
    leaving f.x setting an unconstrained expando, leaving fixed methods
    of C this-bound to c, etc.

I think 2 is simpler but surprising (again consider F.prototype = new
Date in ES3), and I'm not sure how much simpler since the refimpl has
managed to do something different from either of the above (call it
0. Set c.x when evaluating f.x = 4).

Having thought about 1 more, I'd like to point out that it's not
what ES3 does:

js> function F(){}

js> F.prototype = d = new Date

js> f = new F

js> f.proto === d

true js> f.setTime(0)

typein:10: TypeError: Date.prototype.setTime called on incompatible
Object

So new F did create an Object instance, and it linked its proto
(ES1-3 [[Prototype]] internal property) to d. Not what Kris proposes
by analogy to ES1-3, so I'm favoring 2 at the moment.

# Brendan Eich (18 years ago)

On Jul 9, 2007, at 4:16 PM, Brendan Eich wrote:

I think we have only two courses:

  1. Kris's proposal to make F construct instances of C. If C is not
    dynamic, these instances can't be decorated with expandos by (new
    F) or its callers.

  2. Your proposal to avoid any magic implication of F.prototype = c,
    leaving f.x setting an unconstrained expando, leaving fixed methods
    of C this-bound to c, etc.

Sorry, the "leaving fixed methods of C this-bound to c" is a red
herring. It's true that extracting c.m or F.prototype.m would get a
bound method reference which when called would bind |this| to c --
but that's not relevant to a delegated call f.m(), which would try to
pass f, and then run into the type constraint on |this| that's
implicit in all fixed (non-prototype qualified) methods (functions
defined in class bodies).

Hope this is all clear now :-P.

# P T Withington (18 years ago)

On 2007-07-09, at 19:16 EDT, Brendan Eich wrote:

Having thought about 1 more, I'd like to point out that it's not
what ES3 does:

js> function F(){} js> F.prototype = d = new Date js> f = new F js> f.proto === d true js> f.setTime(0) typein:10: TypeError: Date.prototype.setTime called on incompatible
Object

So new F did create an Object instance, and it linked its proto
(ES1-3 [[Prototype]] internal property) to d. Not what Kris
proposes by analogy to ES1-3, so I'm favoring 2 at the moment.

[Coming late to the party]

And it has burned me that I could not do that. I wanted to write an
'annotated string' and couldn't because of exactly this restriction.
Will I be able to subclass built-in types? Or will their methods be
hard-wired in some way that raises similar issues?

# Brendan Eich (18 years ago)

On Jul 9, 2007, at 4:32 PM, P T Withington wrote:

On 2007-07-09, at 19:16 EDT, Brendan Eich wrote:

Having thought about 1 more, I'd like to point out that it's not what ES3 does:

js> function F(){} js> F.prototype = d = new Date js> f = new F js> f.proto === d true js> f.setTime(0) typein:10: TypeError: Date.prototype.setTime called on incompatible Object

So new F did create an Object instance, and it linked its proto (ES1-3 [[Prototype]] internal property) to d. Not what Kris proposes by analogy to ES1-3, so I'm favoring 2 at the moment.

[Coming late to the party]

And it has burned me that I could not do that. I wanted to write an 'annotated string' and couldn't because of exactly this restriction. Will I be able to subclass built-in types?

Subclass, yes -- provided the superclass is not final.

Subclassing is not what's going on above, though.

Or will their methods be hard-wired in some way that raises similar issues?

Certainly not for two classes, one a subclass of the other. There you
get the usual OOP object calculus.

# Brendan Eich (18 years ago)

On Jul 9, 2007, at 4:40 PM, Brendan Eich wrote:

And it has burned me that I could not do that. I wanted to write an 'annotated string' and couldn't because of exactly this restriction. Will I be able to subclass built-in types?

Subclass, yes -- provided the superclass is not final.

I should have added that String is not final in ES4 but string is.
Yeah, we have had to add back primitive types -- they're wired deeply
into the language. The pain should be less this time, though. And the
final class string extends String, so you can decorate
String.prototype with new function-valued properties and primitive
strings can be operated on by those methods.

Similarly, boolean <: Boolean.

The current reference implementation makes {int,uint,double,decimal}
<: Number, but we hope to change that while preserving backward
compatibility, by making all of {int,uint,double,decimal,Number}
peers and wiring up the prototype properties of the first four to the
object denoted by Number.prototype. That's actually more compatible
if you study it a bit. ES3 today (SpiderMonkey):

js> i = 42

js> i.proto === (3.14).proto

true js> i.proto === Number.prototype

No extra layer of int.prototype between i.proto and
Number.prototype.

So we will make the same fix to boolean and string -- they won't have
their own class prototype objects, rather they'll share their
respective capitalized super-class's prototype. This means that
boolean need not extend Boolean, nor string extend String, which will
avoid some ugly bootstrapping magic.

People do use String and Number prototyping of primitive values
(whether int or double), so compatibility is worth a special case
here when setting up the built-in classes.

# Kris Zyp (18 years ago)

You are right, it doesn't work for a Date, but it does work on an Array. The incompatibility of calling setTime on the Date instance seems to have more to do with the natural inclination to make primitives classes "final" (I am not sure if they are in ES4, but in Java they are final) than class method binding. While I am still hoping for option 1, I will say that throwing an (incompatible object) error on f.m() seems more consistent than the other possibilites that I have been countering: non-shallow assignments, automatic this-binding, or preventing the assignment of prototype objects that happen to be instances of user defined classes. Kris

# Brendan Eich (18 years ago)

On Jul 9, 2007, at 4:51 PM, Kris Zyp wrote:

You are right, it doesn't work for a Date, but it does work on an
Array.

Only if you don't call toString or toLocaleString.

Most Array and String prototype methods are intentionally generic.
Too bad we didn't generalize this all over the place in 1996 (sorry,
Tucker). To call such methods on other types of objects, in ES1-3 you
have to use .apply or .call -- in ES4 there are Array.slice, .e.g.,
counterparts that take a leading explicit this-object parameter for
Array.prototype.slice.

The incompatibility of calling setTime on the Date instance seems
to have more to do with the natural inclination to make primitives
classes "final" (I am not sure if they are in ES4, but in Java they
are final) than class method binding. While I am still hoping for
option 1, I will say that throwing an (incompatible object) error
on f.m() seems more consistent than the other possibilites that I
have been countering: non-shallow assignments, automatic this- binding, or preventing the assignment of prototype objects that
happen to be instances of user defined classes.

Yeah.

# Kris Zyp (18 years ago)

I think it seems worth considering what a programmer is intending if writes a function and defines a prototype for it. This is an explicit action to create objects that inherit from the prototype object. If a user has specifically written code to inherit from c, wouldn't he expect to inherit everything from c, including fixtures and methods (that work, not throw incompatibility errors)? If we access fixture properties and methods just as we do do expando properties and functions, why wouldn't we expect programmers to want and fixtures and methods to be inherited? Defining prototypes is not something that just automatically happens in places, but is a very intentional definition on the part of a programmer in which they want and expect inheritance. And I still appeal to the symmetry and consistency of the "is" operator operating exactly the same as the old instanceof operator when the testing against a class: (a is C) == C.prototype is in a's delegate chain (at least when proto has not been externally modified) that would be true with option 1. Kris

----- Original Message ---

# Brendan Eich (18 years ago)

On Jul 9, 2007, at 9:09 PM, Kris Zyp wrote:

I think it seems worth considering what a programmer is intending
if writes a function and defines a prototype for it. This is an explicit
action to create objects that inherit from the prototype object. If a user has specifically written code to inherit from c, wouldn't he expect to
inherit everything from c, including fixtures and methods (that work, not
throw incompatibility errors)?

Because it's possible to violate the type constraints on fixtures in
the non-subclass object created by the function constructor?

That seems like a real possibility. The class methods, unless
qualified by prototype, really do constrain the type of |this|, and
that's a big part of the value of classes. Consider the alternatives:

  1. Classes do not constrain types, have to check everything that
    might be different in a delegating object method call (|this|,
    members) at runtime.

  2. Delegate creation via (new F) given F.prototype = new C and class
    C must make an instance of C, not of Object. This is not backward
    compatible.

  3. Delegating to class methods from an instance created via (new F)
    somehow (how?) wraps the class methods with type-constraining
    wrappers, so the cost is not born by the class methods when called
    class instances.

I don't think any of these is a good plan.

If we access fixture properties and methods just as we do do expando properties and functions, why wouldn't we expect programmers to want and fixtures and methods to be inherited?

Because fixture means fixed, unlike expando or ad-hoc properties. If
you want to delegate without type constraints, you have the tools in
JS already. Using classes means either locking down fields and
methods, or (if you use dynamic and prototype qualifiers) emulating
the ES1-3 builtins and stuff like the DOM classes. In the former
case, you should get a type error. In the latter, you can do what you
say you want, but you have to write the class with extra qualifiers.

Defining prototypes is not something that just automatically happens in
places, but is a very intentional definition on the part of a programmer in
which they want and expect inheritance.

So is defining a class.

So is mixing the two definitions, constructor function prototype (not
a definition but an assignment expression, of course) and class
definition.

And I still appeal to the symmetry and consistency of the "is"
operator operating exactly the same as the old instanceof operator when the
testing against a class: (a is C) == C.prototype is in a's delegate chain (at least when
proto has not been externally modified) that would be true with option 1.

The is operator tests whether its left operand is an instance of a
subtype (<:, which is reflexive, so includes the type itself) of its
right operand. It is not instanceof, which tests whether its left
operand has a prototype equal to the value of the 'prototype'
property of its right operand (if that operand is an object). Don't
mention mutable proto (it's a botch and irrelevant). Just consider:

function B() {} function D() {} D.prototype = new B d1 = new D assert(d1 is B) // false assert(d1 instanceof B) // true D.prototype = new Object d2 = new D assert(d2 is B) // false assert(d2 instanceof B) // false

class B {} class D extends B {} d1 = new D assert(d1 is B) // true assert(d1 instanceof B) // true D.prototype = new Object // silently fails because 'prototype' // is DontDelete and ReadOnly in D d2 = new D assert(d2 is B) // still true assert(d2 instanceof B) // still true

Not only can't you modify D.prototype, but (d1 is B) is true for
class B but false for function B.

You are right that is and instanceof agree in all four assertions for
classes. But only one of four corresponding assertions for
constructor functions is true. Classes are not functions, not by a
long shot.

# Kris Zyp (18 years ago)

Because it's possible to violate the type constraints on fixtures in the non-subclass object created by the function constructor?

Not if the instance of F is a instance of C.

  1. Classes do not constrain types, have to check everything...

Certainly don't want that. However, checking to see if an incompatible method error needs to be thrown on every method call requires extra checks as well doesn't it?

  1. Delegate creation via (new F) given F.prototype = new C and class

C must make an instance of C, not of Object. This is not backward compatible.

Why not make (new F) be an instance of C if C is a subclass of Object, otherwise make it an instance of Object? That would maintain backwards compatibility wouldn't it?

In the former

case, you should get a type error. In the latter, you can do what you say you want, but you have to write the class with extra qualifiers.

Type errors is what I want. I would want (new F).x = 4 to throw a type error if x is defined as a String in C (or I guess if x is defined as a String, ES4 fixtures cause an implicit type conversion, at least in the ref impl, but type errors would surely be in order at some point).

Kris

# Brendan Eich (18 years ago)

On Jul 10, 2007, at 8:21 AM, Kris Zyp wrote:

Because it's possible to violate the type constraints on fixtures in the non-subclass object created by the function constructor? Not if the instance of F is a instance of C.

  1. Classes do not constrain types, have to check everything...

Certainly don't want that. However, checking to see if an
incompatible method error needs to be thrown on every method call
requires extra checks as well doesn't it?

The class methods can't be extracted or called without this binding
to the object from which they were extracted or in which they were
found, so there's no type check. From the reference implementation:

class C { public var x : string; public function m(y) { x = y } } c = new C [object C]

c.x = 42

42

function F() {} F.prototype = c [object C]

f = new F [object Object]

f.x

42

f.m(43) f.x

43

c.x

43

  1. Delegate creation via (new F) given F.prototype = new C and class C must make an instance of C, not of Object. This is not backward compatible.

Why not make (new F) be an instance of C if C is a subclass of
Object, otherwise make it an instance of Object?

Every type is a subtype of Object except undefined (the type;
undefined is the name of the type and its one value), so the
otherwise to that if would never be taken.

That would maintain backwards compatibility wouldn't it?

No -- see SpiderMonkey excerpt from a previous message:

js> function F(){}

js> F.prototype = d = new Date

js> f = new F

js> f.proto === d

true js> f.setTime(0)

typein:10: TypeError: Date.prototype.setTime called on incompatible
Object

In the former case, you should get a type error. In the latter, you can do what you say you want, but you have to write the class with extra qualifiers.

Type errors is what I want. I would want (new F).x = 4 to throw a
type error if x is defined as a String in C (or I guess if x is
defined as a String, ES4 fixtures cause an implicit type
conversion, at least in the ref impl, but type errors would surely
be in order at some point).

Wanting a type error instead of a conversion is a separate topic, so
let's talk about it in another thread. See also

bugs.ecmascript-lang.org/ticket/113

Having long ago proposed separate is/as/to annotations for subtype- test-or-null, subtype-or-throw, and conversion, I'm not in favor of
more than : annotations. And we need converting annotations to self- host ES1-3 builtins, the DOM, etc. But please do start another
thread if you can suggest something to handle that use case and the
one you have in mind. First a statement of the use-case you have in
mind would be helpful.

# Kris Zyp (18 years ago)

The class methods can't be extracted or called without this binding to the object from which they were extracted or in which they were found, so there's no type check. From the reference implementation:

I thought we had agreed that |this| being c instead of f in f.m() was too much of a usability hazard. I guess not (although I still think it is). Do we agree to shallow assignments? class C { public var x : string; public function m(x) { return x} } c = new C c.x = 42; function F() {} f = new F f.x = 43; // doesn't affect c f.m() -> 42? // |this| is c?

typein:10: TypeError: Date.prototype.setTime called on incompatible Object

Is there other implications of having instances (of function constructors) of classes instead of Object for maintaining backwards compatibility besides primitive's method calls? Couldn't this be fixed easily with: setTime = function() { if (this.proto != Date.prototype) throw IncompatibleError ... }

Wanting a type error instead of a conversion is a separate topic, so let's talk about it in another thread. See also

Sorry, I didn't want to breach that subject at all, I was just noting that my example wasn't perfectly correct in how the fixtures would react.

# Brendan Eich (18 years ago)

On Jul 10, 2007, at 9:20 AM, Kris Zyp wrote:

The class methods can't be extracted or called without this
binding to the object from which they were extracted or in which they were
found, so there's no type check. From the reference implementation: I thought we had agreed that |this| being c instead of f in f.m()
was too much of a usability hazard. I guess not (although I still think it
is).

It's an open issue. I sent one message making it sound like the RI
does what you want now, but it just ain't so. Sorry for any confusion.

Here are the tickets I've filed:

bugs.ecmascript-lang.org/ticket/141, bugs.ecmascript-lang.org/ticket/142

Please feel free to comment.

# Kris Zyp (18 years ago)

So does this mean that the verdict has been reached for whether or not new objects from function constructors can be classes? It looks like from the bug report that |this| binding will occur to the class instance. What is still open for comment? Kris

----- Original Message ---

# Brendan Eich (18 years ago)

On Jul 21, 2007, at 7:13 AM, Kris Zyp wrote:

So does this mean that the verdict has been reached for whether or
not new objects from function constructors can be classes?

I pointed out that ES3 does not do what you seem to want here
(functions as auxiliary constructors for classes). In ES1-3, new F
always makes an Object. It doesn't matter if F.prototype = new
String. So we're not going to change this incompatibly for ES4.

It looks like from the bug report that |this| binding will occur to the class instance.
What is still open for comment?

Incompatible changes without new syntax to trigger them are beyond
the pale. Other issues such as how |this| binds, a grand unified
theory of |this| binding, are good trac ticket (and indeed es4- discuss first) fodder.